method

named_scope

Importance_5
Ruby on Rails latest stable (v2.3.4) - 10 notes - Class: ActiveRecord::NamedScope::ClassMethods
named_scope(name, options = {}, &block) public

Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query, such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions.

  class Shirt < ActiveRecord::Base
    named_scope :red, :conditions => {:color => 'red'}
    named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
  end

The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}).

Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count, Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable; Shirt.red.each(&block), Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really was an Array.

These named \scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only. Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count).

All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to has_many associations. If,

  class Person < ActiveRecord::Base
    has_many :shirts
  end

then elton.shirts.red.dry_clean_only will return all of Elton’s red, dry clean only shirts.

Named \scopes can also be procedural:

  class Shirt < ActiveRecord::Base
    named_scope :colored, lambda { |color|
      { :conditions => { :color => color } }
    }
  end

In this example, Shirt.colored('puce') finds all puce shirts.

Named \scopes can also have extensions, just as with has_many declarations:

  class Shirt < ActiveRecord::Base
    named_scope :red, :conditions => {:color => 'red'} do
      def dom_id
        'red_shirts'
      end
    end
  end

For testing complex named \scopes, you can examine the scoping options using the proxy_options method on the proxy itself.

  class Shirt < ActiveRecord::Base
    named_scope :colored, lambda { |color|
      { :conditions => { :color => color } }
    }
  end

  expected_options = { :conditions => { :colored => 'red' } }
  assert_equal expected_options, Shirt.colored('red').proxy_options
Show source
Register or log in to add new notes.
July 24, 2008 - (v2.1.0)
18 thanks

automatically generate scopes for model states

or better known as "throw on some more tasty meta-programming" :). Given an example of a model which has a state (String) which must from a set of defined values, e.g. pending, approved, denied.

 class User < ActiveRecord::Base
   STATES = [ 'pending', 'approved', 'denied' ]

   validates_inclusion_of :state, :in => STATES

   # Define a named scope for each state in STATES
   STATES.each { |s| named_scope s, :conditions => { :state => s } }
 end

This automatically defines a named_scope for each of the model states without having to define a named_scope manually for each state (nice and DRY).

June 27, 2008
13 thanks

Passing find() arguments

I you need to pass additional arguments to a scope (e.g. limit), do this:

  Shirt.colored('red').all(:limit => 10)
March 10, 2009 - (>= v2.1.0)
5 thanks

Use lambda to avoid caching of generated query

If you’re using a named_scope that includes a changing variable you need to wrap it in a lambda to avoid the query being cached and thus becoming unaffected by future changes to the variable, example:

  named_scope :translated, :conditions => { :locale => I18n.locale }

Will always return the same locale after the first hit even though I18n.locale might change. So do this instead:

  named_scope :translated, lambda { { :conditions => { :locale => I18n.locale } } }

Ugly, but at least it’s working as we expect it…

December 12, 2008
4 thanks

acts_as_state_machine named scopes

If you are using the acts_as_state_machine plugin, this will generate all named scopes for your various states.

Place it after the acts_as_state_machine and state declarations.

  class Task < ActiveRecord::Base
    acts_as_state_machine :initial => :waiting
    state :waiting
    state :running
    state :finished

    states.each { |s| named_scope s, :conditions => { :state => s.to_s } }
  end

Then doing a Task.waiting will return the corresponding tasks.

March 21, 2009
2 thanks

Passing optional arguments with defaults to a named_scope

An easy way to do this. (This also shows how you can use joins in a named_scope as well.)

  Class User << ActiveRecord::Base
  belongs_to :semester

  named_scope :year, lambda { |*year|
    if year.empty? || year.first.nil?
      { :joins => :semester, :conditions => ["year = #{CURRENT_SEMESTER}"]}
    else
      { :joins => :semester, :conditions => ["year = #{year}"]}
    end
    }

  end

You can then call:

  User.year     # defaults to CURRENT_SEMESTER constant
  User.year()  # same as above
  User.year(nil)  # same as above; useful if passing a param value that may or may not exist, ie, param[:year]
  User.year(2010)
March 21, 2009
1 thank

Generating empty conditions

In some cases, you might find it useful for your lamba to generate empty conditions based on the passed parameter.

  Class Article << ActiveRecord::Base

  named_scope :category, lambda { |cat|
    if cat == :all
      { :conditions => {} }
    else
      { :conditions => { :category_id => cat } }
    end
  }

  end

Allows you to call something like this:

  categories = user_is_admin ? :all : @current_category
  Article.category(categories)

Mostly useful when chaining named_scopes together. Avoids more complicated if statements.

October 30, 2008
0 thanks

Anyone know the order the scopes assemble conditions?

It seems like last scope = first condition in sql. Can anyone confirm?

March 21, 2009
0 thanks

Remember, named_scope returns an array

named_scope always returns a named_scope object, that acts like an array, even if you’re using it to only find one record. So if you’re trying to perform an association on the results of a named_scope, use the first method to return the model object and not the named_scope object.

Ie:

  user = User.my_name_scope
  user.articles   # assuming User has_many Articles

will return an error. use this instead:

  user = User.my_named_scope.first
  user.articles

(Of course this is a poor example because what you should be doing is performing the named_scope on Article with user as the condition, instead of on User. But if you do need to use the results of a named_scope to perform an association call, you have to do it this way to avoid an error.)

February 16, 2010 - (>= v2.1.0)
0 thanks

Extract the aggregated scoping options

If you want to get the aggregated scoping options of a chain of named scopes use ActiveRecord::Base.current_scoped_methods

It works in the fashion of:

  Shirt.red.medium.alphabetical.current_scoped_methods
  # ==>
  {
    :create => {},
    :find => {
      :conditions => {:color => 'red', :size => 'medium'},
      :order => 'shirts.name ASC'
    }
  }
March 4, 2010
0 thanks

AASM named scopes

If you are using the aasm plugin/gem, this will generate all named scopes for your various states.

Code example

  Class Article <  ActiveRecord::Base

    include AASM

    aasm_initial_state :created

    aasm_state :published
    aasm_state :unpublished
    aasm_state :deleted
    aasm_state :created

    aasm_event :publish do
      transitions :to => :published, :from => [:created]
    end

    aasm_event :unpublish do
      transitions :to => :unpublished, :from => [:created, :published]
    end

    aasm_event :delete do
      transitions :to => :deleted, :from => [:published, :unpublished]
    end

    aasm_states.each { |s| named_scope s, :conditions => { :state => s.to_s } }

  end