What's New in Edge Rails: Has Finder Functionality 12

Posted by ryan
at 11:37 AM on Monday, March 24, 2008



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

Comments

Leave a response

  1. BenMarch 24, 2008 @ 12:28 PM

    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!

  2. MattMarch 24, 2008 @ 05:42 PM

    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.

  3. ZargonyMarch 24, 2008 @ 06:55 PM

    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.

  4. Arie Kusuma AtmajaMarch 26, 2008 @ 12:02 PM

    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 }
  5. ryanMarch 27, 2008 @ 10:31 AM

    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 #=> 23

    or

    user.active.recent.sum(:age) #=> 391

  6. PratikMarch 27, 2008 @ 08:56 PM

    Ryan,

    named_scope :recent, :conditions => ['created_at > ?', 1.week.ago]

    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 :

    named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.minute.ago] } }
  7. RyanMarch 27, 2008 @ 10:40 PM

    Pratik – great point. I shall adjust my examples…

  8. ArunApril 01, 2008 @ 02:05 AM

    Really good feature… i like it very much… Thanks for the update Mr.Ryan…

  9. JamesApril 03, 2008 @ 12:34 PM

    Man, wish I would’ve found out about has_finder earlier! This is great!

  10. LarApril 08, 2008 @ 03:49 AM

    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.

  11. RailsApril 09, 2008 @ 03:02 AM

    Very nice, just read about it on the official Rails Site. thanks for your examples! Daniel

  12. Deepak JoisApril 12, 2008 @ 08:51 AM

    It does not support other options supported by find like :include (but order is supported). Any idea about that?

Comment