New in Rails 2.1: named_scope

posted by andy, Sun Jun 01 22:30:00 UTC 2008

Rails 2.1 Released

If you haven't heard, the release of Rails 2.1 was announced during core member Jeremy Kemper's keynote Saturday morning (but it didn't actually get released until around 2am the next morning).

named_scope

One of my favorite additions to the framework is the absorption of the has_finder plugin into the framework. If you've used has_finder in the past the only thing you'll need to do in order to 'go native' with it in your application is replace the 'has_finder' invocations with 'named_scope'. If you are not familiar with has_finder, it gives you the ability to declare custom finders in a concise, semantically meaningful way. For example:

class Task < ActiveRecord::Base
  named_scope :incomplete, :conditions=>{:completed_at=>nil}
end

In the Grade class above, we've used named scope to add a class-level finder called 'incomplete' that will perform the equivalent of this:

Task.find(:all, :conditions=>{:completed_at=>nil})

Great, so now I can write Task.incomplete and get a list of the incomplete tasks. But so what? I could have written a class-level method myself. Is this anything more than syntactic sugar? Yes!

named_scopes can combine

The real beauty of named scopes is that they chain together. Well crafted named_scopes are fine-grained pieces of find parameters that have clear purposes and meaninful names. Consider these:

class Task < ActiveRecord::Base
  named_scope :incomplete, :conditions=>{:completed_at=>nil}
  named_scope :past_due, lambda{ {:conditions=>['due_on < ?', Date.today]}}
  named_scope :due_today, lambda{ {:conditions=>['due_on = ?', Date.today]}}
end

Task.incomplete.past_due is the same as Task.find(:all, :conditions=>['completed_at is NULL and due_on < ?', Date.today]

Sweet. The chaining of the named_scopes means that you can create really nice 'sentences' in your Rails code that is clear and easy to read.

named_scopes play nicely with association proxies

Even better, the named_scopes work through association proxies as well. Without getting into the details too much, assume that your User class has_many Tasks. Now, your boss wants you to help him through a commons scenario. "Ryan, how can I figure out how many tasks that slacker Chris has let slide. Easy.

@chris = User.find_by_name('chris')
@chris.tasks.incomplete.past_due

In the first place I used this, the code became a lot easier to read. I added a named_scope that at first did not make a lot of sense: it added a model-related scope to the find. In this case it helped the code because I was normally accessing the data through another 'owner' and this named_scope helped me chain in finer focus.

@grades ||= @student.unreported_grades.find(:all, :conditions=>{:subject_id=>params[:subject_id]})
    @grades ||= @student.grades.unreported.for_subject(params[:subject_id])

The commented code is the original version that used a (now deprecated) class-level method. Passing the find conditions there worked but it was a little ugly. The named_scope version is both clearer and easier to maintian.

Filed Under: Rails | Tags:

Comments