Scoping out a model
That didn’t come out right. What I meant to say was there is a great new Rails plugin called scope_out that assists with AR class methods, and you should try it out.
First of all, you shouldn’t have something like Whatever.find(:all, :conditions => 'something IS NOT NULL') in your controllers. That find should be put into the model ( Whatever.find_something instead). Well, scope_out makes that process a little nicer.
I’ll use Golf Trac as an example. So, in Golf Trac, there are 9 hole and 18 hole courses all over the place. At some point, I may want to show a list of each, independent of the other. Here’s what I might do normally:
1 2 3 4 5 6 7 8 9 | class Course < ActiveRecord::Base def self.find_nine find(:all, :conditions => ['is_eighteen = ?', false]) end def self.find_eighteen find(:all, :conditions => ['is_eighteen = ?', true]) end end |
I definitely can’t complain about that, but there’s an even nicer way. Let’s look at how scope_out can help…
1 2 3 4 | class Course < ActiveRecord::Base scope_out :nine, :conditions => { :is_eighteen => false } scope_out :eighteen, :conditions => { :is_eighteen => true } end |
That’s pretty cool. That allows Course.find_nine and Course.find_eighteen just like the regular class method implementation. It creates methods with find_ appended to whatever you’re “scoping out.” Another nice thing about it is you can still pass in additional options to the finder.
1 2 | Course.find_nine(:first) Course.find_eighteen(:all, :order => 'created_at DESC') |
It also allows the ability to use it in a “with_scope” fashion. For instance…
1 2 3 4 | Course.with_eighteen do @recent = Course.find(:order => 'created_at DESC', :limit => 5) end # => five most recent eighteen hole courses |
Dynamic finders also come as a bonus with this plugin. So find_all_by_whatever now has a cousin or two, find_all_nine_by_whatever and find_all_eighteen_by_whatever.
That’s great and all, but the more typical case would be what nine hole and eighteen hole courses a user has. Well, since scope_out works with associations, too, it’s not a problem:
1 2 3 4 5 | current_user.courses.find_nine # => all nine hole courses this user has added current_user.courses.find_eighteen # => all eighteen hole courses this user has added |
There are a few other cool things this plugin supports. If you can look beyond the fact that this doesn’t make any sense, you can see how combining two scopes is done:
1 2 3 4 5 | class Course < ActiveRecord::Base scope_out :nine, :conditions => [ 'is_eighteen = ?', false ] scope_out :eighteen, :conditions => [ 'is_eighteen = ?', true ] combined_scope :both, [:nine, :eighteen] end |
Again, that’s dumb. Course.both is the same as Course.find_all since it’s a boolean condition, but you get the idea. To install with_scope:
script/plugin install http://scope-out-rails.googlecode.com/svn/trunk/
That will put a “trunk” folder in your plugins directory, which you should rename. Enjoy.

Chris Thursday, 25 Oct, 2007 Posted at 05:25AM
I like it ;-) I’m sort of on a quest to get my controllers on a diet – I can’t stand to look at some of them, they’re so obese.
Now I have one for you to aid your journey to REST: resources_controller. I just tried it out and I love how much code it removes from the controller.
Dave O. Thursday, 25 Oct, 2007 Posted at 05:43AM
Hey, I resemble that remark Chris ;p
Ryan, very cool article. Thanks.
Ryan Thursday, 25 Oct, 2007 Posted at 06:45AM
Whoa, that looks sweet. It should definitely help in the conversion.
I haven’t converted to REST just yet, but the other evening (when you mentioned the noun rule) I went through everything (controllers, models, helpers, views) and restructured it all. It feels much cleaner now.
It’s amazing how much you can improve over just a couple of months. I should start doing a mandatory re-factor every 3 months—I bet I’d be surprised at the “old” code every time.