- We design and build extraordinary applications for companies looking to make the next great idea a reality.
- learn more
Named Scopes Are Awesome

My favorite new Rails 2.1 feature is named scopes (previously available as the has_finder plugin. Scopes have already completely changed the way I write complex find logic in my ActiveRecord models.
The basic idea is simple. ActiveRecord has grown a new class method named_scope, which lets you map a set of find criteria to a name:
named_scope :active, :conditions => {:is_active => true}
At which point active is available to all instances of that ActiveRecord class, user.active.
(About the picture -- it's a periscope. With a name. Get it? Okay, you try and come up with a picture that symbolizes named scopes, especially since I just did a name tag bit a couple of weeks ago...)
So far, this is almost completely equivalent to the following class method:
def self.active find_by_is_active(true) end
The scopes don't have to be conditions, but can be any find options:
named_scope :by_name, :order => "name ASC"
So far, that's not very exciting, but here are two more named scope features. First, the scopes can be dynamic and take arguments:
named_scope :recent, lambda { |days| :conditions => ["date_created > ?", days.ago]}
Second, and most importantly, the scopes can be composed:
user.active.by_name user.recent.active
This is where the real power of named scopes comes in: you can split complex logic into small pieces that can be put together cleanly to make the multi-part search.
For example: ActiveRecord provides a nice shortcut for describing a search where all the criteria are based on equals -- you can enter the conditions as a hash (such as {:is_active => true, :last_name => 'rappin'}. But if that second criteria is an SQL LIKE command or something, then you're back to composing SQL conditions. Plus, I can never remember exactly how to format a LIKE command. But with named scopes, this becomes a snap. The active scope, we just wrote, now, add this:
named_scope :contains, lambda { |column, text| {:conditions => ["lower(#{column}) LIKE ?", "%#{text.downcase}%"]} }
The LIKE version of the query can now be written as:
user.active.contains(:last_name, "rappin")
And if I want the results to be sorted, I just add another scope:
user.active.contains(:last_name, "rappin").by_name
I find that to be readable and much clearer in describing the intent of the code.
Another nice feature of named scopes is that they maintain their find options in an attribute called proxy_options allowing for some amount of testing of the behavior of the scope:
should "correctly generate conditions for a contains scope" do expected = {:conditions => [lower(last_name) LIKE ?", "rappin"]) assert_equal(expected, user.contains(:last_name, "rappin").proxy_options end
One gotcha in that testing process is that each scope only contains its own options in that attribute, not the merged set of all composed options. Each option maintains an attribute which points to the earlier scope, called proxy_scope, so you can test the outer part of the composed scope as follows:
should "correctly generate a composed scope" do actual = user.is_active.contains(:last_name, "rappin") expected = {:conditions => {:is_active => true}} assert_equal(expected, actual.proxy_scope.proxy_options end
Another thing to keep in mind for composed named scopes is that internally, they are implemented as nested calls to the existing ActiveRecord with_scope method. This means that the with_scope rules for composing options apply. Any :include options are merged together and duplicates removed, any conditions option are merged together and connected with logical and operators, and for all other options (such as :order the last scope to set the option wins.
There's another neat trick to scopes -- you can create anonymous ones on the fly. Check out this Ryan Bates screencast on the topic, then come back here in a few days when I take that mechanism another step or two farther.
Topics: Ruby on Rails
Comments: 12 so far
Leave a comment
About Pathfinder
Recent
- Firefox Plugin Malware ‘Trojan.PWS.ChromeInject.A’
- Pathfinder releases version 1 of the its Flash Platform microsite (codename Mica)
- Pimp my Rails: Five Plugins & Gems to Make Rails Better
- iPhone: Using Pre-processor Directives for Device Testing
- Subtle OpenGL Projection Matrix Difference Between iPhone Simulator and Device
- App Security: Throw Out the Org Chart!
- Pimp my jQuery: Five plugins to replace the features Prototype and Scriptaculous users expect
- Thanksgiving 2008: What We’re Thankful For (In Rails)
- iPhone SDK: Testing with TextMate & GTM
- GWTQuery - JQuery-like Syntax in GWT
Archives
- December 2008
- November 2008
- October 2008
- September 2008
- August 2008
- July 2008
- June 2008
- May 2008
- April 2008
- March 2008
- February 2008
- January 2008
- December 2007
- November 2007
- October 2007
- September 2007
- August 2007
- July 2007
- June 2007
- May 2007
- April 2007
- March 2007
- February 2007
- January 2007
- December 2006
- November 2006
- October 2006
- September 2006
- August 2006
- July 2006
- June 2006
- May 2006
- April 2006
- March 2006


Gotta love ‘em!
I hope for round two, they get rid of the ‘lamba’ nonsense and just accept a &block. Much more rails-ish.
Comment by Karl, Friday, June 20, 2008 @ 5:57 pm
Nice article. I’ve been putting off upgrading my 1.x application, but you’ve just given me more incentive to do so.
Comment by Harry Bailey, Friday, June 20, 2008 @ 6:02 pm
[...] Pathfinder Development ยป Named Scopes Are Awesome Named Scopes Are Awesome (tags: activerecord rails ruby) SHARETHIS.addEntry({ title: “links for 2008-06-21″, url: “http://blog.libinpan.com/2008/06/21/links-for-2008-06-21/” }); [...]
Pingback by links for 2008-06-21 | Libin Pan, Saturday, June 21, 2008 @ 1:44 am
Nice post - I’m loving named_scope aswell - realised the other day that it’s easy to use them with has_many associations.
Comment by Darragh Curran, Saturday, June 21, 2008 @ 7:05 am
Don’t forget how well this blends into things like pagination:
@users = User.active.by_name.paginate(
:page => params[:page],
:order => ‘created_on DESC’,
:per_page => (params[:users_per_page] || 12)
)
For example
I also like how I could pull an array as well (without making a new named_scope or tinkering with sorts):
@users = User.active.by_name[0..3]
I’m glad this made it into production.
Comment by Michael Christenson II, Thursday, June 26, 2008 @ 9:58 am
Um, just a distinction… In all your examples don’t you mean to use User instead of user? Your calling the method on the Model, not an instance? Correct me if I’m wrong.
Comment by Britt, Thursday, June 26, 2008 @ 12:50 pm
[...] other words, you can augment the contains scope introduced in last week’s post with a regular find method like [...]
Pingback by Pathfinder Development » More Named Scope Awesomeness, Friday, June 27, 2008 @ 2:19 pm
Am I missing something or is: “lower(#{column}) LIKE ?”, “%#{text.downcase}%” opening up the query to sql injection if you pass in something from the user, like: Model.contains(:name, params[:name]). I believe we’d want this instead:
“lower(#{column}) LIKE ?”, “%#{connection.quote_string(text.downcase)}%”
Comment by Drew Blas, Friday, June 27, 2008 @ 7:22 pm
[...] Named scopes in Rails 2.1 : imagine you can get all your recent active users by user.recent.active ? Take a look to this awesome feature. [...]
Pingback by DotMana » » News from RoR world, Thursday, July 10, 2008 @ 8:19 am
[...] Pathfinder Development Round Two… [...]
Pingback by links for 2008-07-10 | iLenceel, Thursday, July 10, 2008 @ 5:49 pm
@Karl lambda allows the lazy evaluation of that code, a block would be ran at loading time..
Comment by Luca Guidi, Tuesday, August 5, 2008 @ 7:03 am
[...] Great resource on named scopes [...]
Pingback by Scott Motte » Blog Archive » Named Scopes, Wednesday, August 27, 2008 @ 6:00 pm