It looks like Nick Kallen’s wildly popular has_finder plugin will be making its way into Rails 2.x in the form of named_scope. Observe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class User < ActiveRecord::Base named_scope :active, :conditions => {:active => true} named_scope :inactive, :conditions => {:active => false} named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } } end # Standard usage User.active # same as User.find(:all, :conditions => {:active => true}) User.inactive # same as User.find(:all, :conditions => {:active => false}) User.recent # same as User.find(:all, :conditions => ['created_at > ?', 1.week.ago]) # They're nest-able too! User.active.recent # same as: # User.with_scope(:conditions => {:active => true}) do # User.find(:all, :conditions => ['created_at > ?', 1.week.ago]) # end |
All the goodness you’ve come to love in has_finder is now available as named_scope – plus you get some extra goodies too. User.all is given to you for free as an alias for User.find(:all).
Advanced
For those with more discriminating needs, don’t forget some of these has_finder tidbits:
Passing Arguments
Pass in arguments to your named scopes to specify conditions (or other props) at run-time.
1 2 3 4 5 |
class User < ActiveRecord::Base named_scope :registered, lambda { |time_ago| { :conditions => ['created_at > ?', time_ago] } end User.registered 7.days.ago # same as User.find(:all, :conditions => ['created_at > ?', 7.days.ago]) |
Named Scope Extensions
Extend named scopes (in a similar fashion to association extensions).
1 2 3 4 5 6 7 8 9 10 |
class User < ActiveRecord::Base named_scope :inactive, :conditions => {:active => false} do def activate each { |i| i.update_attribute(:active, true) } end end end # Re-activate all inactive users User.inactive.activate |
Anonymous Scopes
You can also pass around scopes as first class objects using scoped (a named scoped provided to you for free) as a way to build hairy queries on the fly.
1 2 3 4 5 6 7 8 9 |
# Store named scopes active = User.scoped(:conditions => {:active => true}) recent = User.scoped(:conditions => ['created_at > ?', 7.days.ago]) # Which can be combined recent_active = recent.active # And operated upon recent_active.each { |u| ... } |
named_scope is a truly great feature. If you haven’t started using it yet, do so. You won’t know how you lived without it. Major thanks goes out to Nick.
tags: ruby, rubyonrails

I’ve been waiting for this to get integrated – it’s fantastic, and will be a huge boon to Rails developers. Thanks for the update, Ryan!
This looks so incredibly useful. I’m relatively new to Rails and haven’t had a chance to play with the has_finder plugin, so I can’t wait to start using this.
Great. One of the first things I’m doing, when creating a new Rails project, is to install the has_finder plugin. I’m happy to hear that it has been merged into Rails now.
Thanks for the great post, Ryan. Will there be such a convenient shortcut for count specifically and rails statistics methods generally as well?
ex.User.count :conditions => { :active => true, :created_at.gt => 1.week.ago }Arie, yes – these methods are already supported just as they are with association extensions. E.g with my previous user example:
user.active.recent.count #=> 23or
user.active.recent.sum(:age) #=> 391Ryan,
That is really a bad example. As the conditions gets evaluated at load time, it’ll end up giving you inaccurate results in production where models get loaded only once.
Correct way would be :
Pratik – great point. I shall adjust my examples…
Really good feature… i like it very much… Thanks for the update Mr.Ryan…
Man, wish I would’ve found out about has_finder earlier! This is great!
I’m playing around with this right now, and I’m wondering how you would spec something like this out in RSpec if you were following a BDD approach? Or is this one of those things that is tested as part of rails, and I shouldn’t bother with? I would think that if you were using this to put together some complicated queries that there would have to be some sort of tests to ensure you are getting the results you want.
Very nice, just read about it on the official Rails Site. thanks for your examples! Daniel
It does not support other options supported by find like :include (but order is supported). Any idea about that?
wow those rock!
Wow, fantastic! I’ve been dying for a way to do this elegantly for ages. You can’t beat having it right in the soup.
Is there any way to eager load a named_scope from an association ?
Something like
User.find(:all, :include => [:messages.public])
with
User: has_many :messages
Message: named_scope :public, :conditions => {:is_public => true}
Ryan, Looks like you’ve got a syntax error in one of the examples. I believe:
should be:
thank you very much! I have successfully used this! =)
On my website there is a page to view standings for a so called soccer pool. There are member records with a name per pool, that can be left empty. Each member is linked to a user record with a name field as well. In case the name of the member is empty, the user’s name is used in the standings.
When using named_scope, I see strange behavior, since the eager loading isn’t quite as I expected. I guess things have been changed in Rails 2.1 in this area that have not necessarily have to do with named_scope.
Anyway, in my Member class this scope doesn’t work:
named_scope :standings, :include => :user, :order => ‘members.position, COALESCE’
The query doesn’t join with the users table, so a missing column error is returned. But when I add :conditions => ‘users.id = users.id’, it works! So is Rails looking in the conditions to see if a associated table is used?
Anybody see my points and have some clues/comments?
OMG, I just understood how much a nerd I have become.
Can you believe I got so ecstatic about this while reading this introduction (and falling in love with the beautiful code snippets) that I just started to jump around screaming how cool all this is?
It’s true I just can’t believe how cool Ruby is, and the incredible work you guys are doing with Rails. It so just as it’s meant to be. It couldn’t be otherwise.
It just fits.
Keep it rolling guys!
You rock really, really hard…