December 28, 2006 @ 04:29 PM by nap · 0 comments
I just got word that there's a Ruby on Rails user group starting up in my area. It's being organized by Scott Garman and the first meeting will be January 16th at the UNH Library in Durham, NH. More details at Scott's Blog. It'll be great to meet some other local Ruby dorks -- most of my contract work is non-local and the other devs I know in the Portsmouth area have sadly yet to be bitten by the bug. I'm psyched that someone has taken the initiative to set this up. Thanks Scott!
On completely unrelated news, I'll be afk for a few days. Heading northbound to visit relatives and then some college friends for a New Years' extravaganza of sorts. Hopefully I can remember how to play beer die -- it's been awhile. See y'all in 2007!
December 26, 2006 @ 06:06 AM by nap · 0 comments
So it's the holidays, and I've got some time "off" from "work" to recoup and think about my next project, play around with a few ideas, new toys... The first thing on that list of toys is Tom Locke's Hobo framework.
The Hobo 0.4.0 Gem was released late last week and it's pretty slick, although we're warned not to use it in production webapps yet :-). Hobo extends Rails in a number of ways, but the crux of it is that it lets you get up and running with a real, usable (and cleanly coded) Ruby-based webapp quicker than ever before. Hobo bundles in a working user system (acts_as_authenticated), permisisons, and a bunch of other goodies -- take a look at the screencast for a quickie walk-through. There also seem to be some interesting ideas here regarding tag libraries, but that's not covered in this first screencast. Anyway, definitely worth checking out.
December 24, 2006 @ 11:21 AM by nap · 0 comments
Counter cache is my new friend. It's a very important little feature available on ActiveRecord that makes counting associations efficient by maintaining a cache on the model.
Although it's documented in AWDWR (p359 in my shiny new copy of the second edition), I guess it slipped my mind until now, so I thought I'd blog about it just in case anyone else was looking for a solution...
So why does it rock so hard, you ask? Let's say that I'm building YADRC (Yet Another Digg/Reddit Clone). I need to count the votes that users make to determine the popularity of an article, display that number, and use it to rank the order of the stories. So we set up our models: a story model, a vote model, and a user model, and we create the appropriate associations. Our vote.rb is going to look something like this:
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :story
end
Predictably, Story hasmany votes and User hasmany votes. Now we can do something like this to find the number of votes on a story object:
Story.find(4).votes.size
This will work nicely right out of the box. However, once we have a significant amount of stories logged in our system and a reasonable number of votes on each story, performance goes straight to poop. The reason is that each time we're generating a score for a story (and remember, we've got N stories), we're running a query like this against our database:
SELECT count(*) AS count_all FROM votes WHERE (votes.story_id = 4)
If we have any real amount of data in our system, this is going to get really ugly. Counter caching is one way to help counteract this problem. Let's rewrite our model to use it:
class Vote < ActiveRecord::Base
belongs_to :user
belongs_to :story, :counter_cache => true
end
We also have to add a column, called votes_count, to the stories table in our database. Make sure to specify a default value of 0 in your DDL. Then we generate a migration, run it, and now we're ready to try again. The difference should be pretty dramatic. If we tail -f our development log, we'll notice that those count(*) queries aren't getting run anymore. So what's happening?
ActiveRecord is using our counter cache column (called votes_count in this case) on the stories table to store the number of belonging objects on the associate class. This value is incremented when an object of this class is created, and decremented when it's destroyed. The result is that we have a local "cached count" on the Story instance so we don't have to constantly query the votes table directly. Good deal, eh?
There are two additional things worth noting about counter caching. As Dave Thomas points out in AWDWR, the counter won't get updated if entries are added by setting the link to the parent directly in the child like this:
vote = Vote.new
vote.story = story
vote.save
If you're doing it this way, you'll have to force the parent class to refresh the collection. The right approach is to add a Vote through the Story object, which makes the parent aware of the increment (or decrement) and update the counter cache accordingly:
story.vote.create
The other point I wanted to make was that if you call the count method on object instead of using size, it will always run the actual count query on the underlying database, instead of using our cached shortcut. Thanks for the tip on that one, technoweenie.
December 22, 2006 @ 08:52 AM by nap · 0 comments
If you're an IntelliJ user like me, and have recently been bitten by the RoR bug, you'll be glad to know that there's a plugin on the way. It's still in it's infancy, lacking a lot of features, and is therefore not in the repository, but that doesn't mean we can't play around with it. What follows is a brief tutorial on how to locate, install, and use the ruby plugin...
First you need to check out the sources from the Subversion repo:
svn co http://svn.jetbrains.org/idea/Trunk/ruby
Use Ant to build it (run ant from the directory you co'd to, where the build.xml file lives). The build process should generate a jar file in dist/ called ruby-SNAPSHOT.jar.
Next, make a new directory in your IDJ plugins directory called 'ruby' and make a lib directory in there. Copy the jar to $IDJ_ROOT/plugins/ruby/lib. On OS X, this path is something like /Applications/IntelliJ\ IDEA\ 6.0.2.app/plugins/ruby/lib/.
Start IntelliJ. Go to Settings -> Project Settiings -> Project Structure. In Global Resources, under JDKs, right click and add a Ruby SDK. On OS X, you'll want to point it at /usr/local, or whereever your copy of Ruby has been installed.
Now you should be able to create a new project and select Ruby SDK from the JDK list (heh). Create a single module project, select Rails as your module type, and set the Ruby SDK for the module. Finally, you'll be given the chance to generate a new Rails application skeleton, generate missing files, or use an existing Rails app. Go ahead and create a test project and generate a new Rails framework. I'll do all the work for you and populate the project explorer with that oh-so-familiar directory structure. Check out that ugly Ruby icon they're using. Jeeze, what is that?
Right click in the project explorer and select New -> Controller. Name your controller test and add an action name hello. Click OK. IDJ will run the Rails generator and update the project explorer. You now have a TestController with a hello action, the corresponding view template, and the expected test stubs.
You can select Run -> Run... to start a WEBrick server and test your app. There are some bugs here. First of all, there is no run output in the console view where you'd expect it to be. No web browser is launched, no indication is given of what port number the server gets bound to. Worse, you can't seem to kill a running WEBrick server without killing the process from a terminal. So yeah, there's obviously still some work to do here. For now, I'd suggest running WEBrick from the command line (RadRails still wins here, at least). No command completion or real debugging support is available yet either, but hey.. it's a start. It's functional, you can run tests from within the IDE, you get basic code formatting, syntax highlighting, etc.
For more information, check out the JetBrains Project Homepage for the ruby plugin, where you'll find a link to the roadmap and discussion groups. You'll note that there are plans for autocompletion, ability to browse to symbol, proper YAML, RXML support, etc. I really hope that development continues on this. IDJ is much-loved amongst the Java community, and could be a huge hit in the Rails community if they get a slick, fully functional plugin out there.
Develop with pleasure ;-).
December 22, 2006 @ 06:22 AM by nap · 0 comments
Ruby itself doesn't directly support multiple inheritance (yay for mixins) but the community sort of does. If you survey a random group of RoR developers, about half of them seem to come from a structured (oftentimes strongly-typed) OO language like Java, and the other half comes from the scripting world. One of the best things about Ruby is that it appeals equally to both camps. It's a common ground of sorts, a compromise, maybe the best of both worlds. I dig that.
But as everyone knows, different backgrounds beget different toolsets. In the case of Ruby on Rails, it raises the question: do we need a real IDE for this kind of work, or is a simple text editor good enough?
I've gravitated back and forth between the two coding camps over the years. It's a "right tool for the task" sort of thing if you ask me. You want a lightweight project management application that runs on the web? PHP please. You want a robust scalable service for the delivery of digital media and have some money to spend? Let's do that one in Java. There's a religious war to be had here for sure, and I don't want to get into that... The interesting thing (and the point I'm trying to make) is that the tools you come to use and respond to differ depending on that background.
When I'm working in PHP, I use vim. No, really. I tried PHPEclipse and the pluses just didn't outweight the minuses for me. I also tried Komodo. I'm an old school vi hacker, and I do all my sysadmin work in vi, so it became the natural choice. I don't regret it. Well, not often, anyway. After all, PHP is a scripting language, right?
When I'm working in Java, I'm spoiled to death by IntelliJ. Yes, it's expensive. Yes, there's a learning curve. And yes, it's worth every damn penny/hour spent. I've tried Eclipse. And it's alright, but it's not IDJ. The JetBrains guys have delivered what is by far the most intelligent, usable IDE I've ever used. That's my story, and I'm sticking to it.
So is Ruby a scripting language, or is Ruby a proper OO "enterprisey" language? I'd argue that it's both and that it's use dictates the classification. But when you're building Rails apps, you're firmly entrenched in OO/MVC proper webapp territory, and therefore, imho, that demands the use of a proper IDE. Particularly once a project becomes sufficiently large.
So what do I use when it comes to Rails work then? Well, up until now I've been using RadRails with RDT and Eclipse. It works pretty well for the most part. But it's no IntelliJ. We don't, of course, have auto-complete yet, but the syntax highlighting is getting there, the generators and rake tasks are wrapped up nicely, as is run output, test execution, etc etc. It's better, for me, than using vi, by leaps and bounds and it seems to be getting better and better (nice job guys!).
RadRails seems to be the choice for Windows and Linux users, but everyone else I know on OS X seems to be using TextMate. Whoah, what? I've never used TextMate -- am I missing out? But wait, TextMate is just a text editor right? It's NOT an IDE and doesn't aspire to be. Regardless, the popular opinion seems to be that it's "good enough".
So what do you use? Are you, like everyone else out there, a TextMate-phile? If so, why? Don't get me wrong, I understand the allure of a minimalistic text editor for hacking script, but have you been spoiled by something as nice as IDJ in the past? Oh man would I kill for ctrl-space to autocomplete/interrogate an object to discover it's methods..
I wonder if there are a lot of people who have come over from the Java side of things who have switched to TextMate, particularly people who have used IntelliJ in the past. Am I making a blanket assumption that most of the people using text editors to hack Rails are not Java converts? Am I just overcomplicating the whole thing? Is TextMate really "good enough"?
The entire Rails core team purportedly uses TextMate... Maybe I'm just missing something? I'd love to hear your opinions.
UPDATE: I'm using NetBeans now. And it's great.
December 20, 2006 @ 02:40 PM by nap · 0 comments
So we just finished wrapping up work on the app we've been building for the past 8 months. Yay! It's part of a suite of web-powered tools for a certain niche video editing system. Implemented in Java, leveraging Swing, Axis, WebObjects... Great project and totally learned a lot, but sure am relieved to have delivered it. I'm sure there'll be a tweak to make here or there, but you know... It's delivered (rc).
What's next? Well, I'm not entirely sure to be honest. For the first time in my life I'm thinking about abandoning paid work in order to invest a couple solid months of full-time effort working on the Ruby/Rails app that's in my head. Perhaps it's finally time to put my neck out there a little and make it happen. After all, I isn't getting any younger, now is I?
December 11, 2006 @ 10:35 AM by nap · 0 comments
One of the great things about PHP isn't the documentation, per se, but rather the centralized, searchable docs interface at PHP.net. Big thanks to Jeremy Durham for putting together an equivalent Rails resource site. There's obviously still a good bit of work to do before docs are as complete and usable as they could be, but an online searchable repository like this is definitely a step in the right direction. Bookmark it, and add some example code and comments mang.
Oh and while we're talking about documentation (or lack thereof), don't forget to donate to the Rails API docs project at Caboo.se if you haven't already.
December 07, 2006 @ 11:50 AM by nap · 0 comments
So a couple folks have pointed out that the last Rails+YUI example I posted doesn't work in IE. Or Safari. Eek, that's not so good.
Anyway, this post is kind of a hodgepodge documenting the process I went through to fix those issues and clean it up a bit. Maybe more of a 'note to self' than an actual blog entry, so not required reading by any means. Unless you're having issues with IE and Minus MOR that is, in which case the magic word is content-type. I'm embarassed to say how much sleep I lost tracking that one down. Sigh.
class ExampleController < ApplicationController
layout "standard", :except => :add
def show
end
def add
@response.headers['content-type'] = 'text/javascript';
@thing = params[:thing]
end
end
That's our updated ExampleController. Notice that we're setting the content type of the response in the headers now. The default content type appears to be html, instead of text/javascript. Not entirely sure why this is happening at the moment as Minus-R appears to set the content-type in it's render method. But anyway, for whatever reason, Safari and Firefox both work fine, but IE doesn't like it one bit. Of course, instead of warning us (or giving us an option to warn us, for that matter) it simply discards the asynchronous response. Hence, we never see an update. Nice, eh?
I also took the opportunity clean up the rest of our example a little bit. Here's our new layout template:
<html>
<head>
<title>YUI Tester: <%= controller.action_name %></title>
<%= javascript_include_tag "yui/yahoo", "yui/event", "yui/dom", "yui/dragdrop", "yui/connection", "yui/container"%>
<%= stylesheet_link_tag 'yui/container'%>
<script language="javascript">
YAHOO.namespace("yuitest.container");
function init() {
var handleCancel = function() { this.cancel(); };
var handleSubmit = function() { this.submit(); };
var handleFailure = function(o) { alert("failure: " + o.responseText); };
var handleSuccess = function(o) { eval(o.responseText); };
YAHOO.yuitest.container.myDialog = new YAHOO.widget.Dialog("myDialog", {
width: "500px",
modal: true,
visible: false,
fixedcenter: true,
constraintoviewport: true,
draggable: true });
var escKeyListener = new YAHOO.util.KeyListener(document, { keys : 27 },
{fn:handleCancel,scope:YAHOO.yuitest.container.myDialog,correctScope:true} );
YAHOO.util.Event.addListener( 'myDialogForm', 'submit', function(e) {
YAHOO.util.Event.preventDefault(e);
YAHOO.yuitest.container.myDialog.submit();
});
YAHOO.yuitest.container.myDialog.cfg.queueProperty("keylisteners", escKeyListener);
YAHOO.yuitest.container.myDialog.cfg.queueProperty("buttons",
[{ text:"Save", handler:handleSubmit, isDefault:true },{ text:"Cancel", handler:handleCancel } ]);
YAHOO.yuitest.container.myDialog.callback = {
success: handleSuccess,
failure: handleFailure
};
YAHOO.yuitest.container.myDialog.render();
}
function addThing() {
YAHOO.yuitest.container.myDialog.show();
}
YAHOO.util.Event.addListener(window, "load", init);
</script>
</head>
<body>
<div id="main">
<% if flash[:notice] -%>
<div id="notice"><%= flash[:notice] %></div>
<% end -%>
<%= @content_for_layout %>
</div>
</body>
</html>
OK, a bunch of changes there. First, we've removed all the Prototype JS libs because we no longer need it -- YUI's connection manager can take care of this stuff for us, and since we're not using the default behavior of RJS, there are no worries about dependence on Prototype. Next, we've added a couple key listeners on the popup dialog to handle enter (submit) and escape (cancel). Note that we have to use Event.preventDefault() in our enter key listener to suppress the default form submission action. Otherwise, we end up redirected to a new page that just contains our result string, and we don't want that...
Finally, we've also eliminated the clumsy body of the success handler and replaced it with a single statement: eval(o.responseText). Yup, we can just evaluate the JavaScript returned from our Rails app. No need to append a new script tag to the body, yehck. Here's the code that's returned from our add.ejs template, as a reminder (it's unchanged):
document.getElementById('hello_msg').innerHTML = '<%=@thing[:name]%>';
So yeah, it just replaces the inner HTML in the element named hello_msg. Easy enough. The next step here would be to figure out how to encapsulate the stuff in the layout using some sort of helper module or plugin. But that's it for now.
(Progress on my current Rails project has been pretty slow lately, as we're nearing completion on a big client project (a slick Java-based webstart app that's been occupying the majority of my time for the past 6 or so months). It's nice to finally see the light at the end of the tunnel! Hopefully once that wraps, we'll have some significant time to pour into the RoR ideas and prototypes we've been playing around with...)
December 02, 2006 @ 09:28 AM by nap · 0 comments
RJS rocks. It lets me write JavaScript without writing any JavaScript. That's very Zen, and I like it. But sometimes I find myself writing really ugly, narsty things in my RJS templates. Instead of using Ruby to write JavaScript I end up writing JavaScript and appending it to the page with Ruby, especially when client side conditionals are involved. Needless to say, the template code quickly devolves into what can only be described as frankenrubyscript.
To rid ourselves of the monster, take a look at Dan Webb's MinusMOR plugin, which lets you return plain ol JavaScript to the browser using templates with an .ejs file extension. This may not seem like a big deal but it's pretty damn helpful if you return any significant amount of client-side logic within RJS.
[Note that you'll need to be on the Rails 1.2 codebase to use the plugin]
As a simple example, let's overhaul our sample YUI test app to use MinusMOR. Now this really isn't a great example, as the JavaScript code is super simple and therefore lends itself well to being written in Ruby. But oh well. At least it gives you an idea of how it works.
Our add.rjs file used to contain the following one-liner:
page.replace_html 'hello_msg', @thing[:name]
Now, our add.ejs file will contain the following code instead. Note the use of ERb in the template:
document.getElementById('hello_msg').innerHTML = '<%=@thing[:name]%>';
In this case the normal RJS approach is quicker, sexier, and, above all else, easier to read. And, admittedly, 90% of your client-side code will more than likely be the simple kind of stuff that RJS rocks at (update this text within that DIV, switch the visible state of that DOM element to hidden, etc). But you can certainly imagine scenarios where it'd make sense to write pure JavaScript in the template instead.
Use RJS wherever you can, but once bits and pieces of frankenrubyscript start sneaking into your code, be sure to check out Dan's plugin. Cuz there's no denying that there are cases where using plain ol JavaScript is cleaner, as un-Zen-like as that may seem....!