named_scope
named_scope(*args, &block)No documentation available.
# File activerecord/lib/active_record/named_scope.rb, line 123
def named_scope(*args, &block)
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
scope(*args, &block)
end 14Notes
Passing find() arguments
I you need to pass additional arguments to a scope (e.g. limit), do this:
Shirt.colored('red').all(:limit => 10)
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...
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.
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)
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'
}
}
How using Array methods
It's not possible to use Array methods with a scope because it's not an Array but an ActiveRecord::NamedScope::Scope :
this Article.promotion.sum(&:price) doesn't run.
But you can use the to_a method to transform the ActiveRecord::NamedScope::Scope to an Array :
Article.promotion.to_a.sum(&:price)
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.
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
named_scopes and Acts as State Machine
As of AASM 2.1 named_scope(s) are defined by default for each state of a model.
Testing Named Scopes
Thanks for the example of testing named_scopes. Being new to Rails, I struggled to find examples that I could understand. Here is another "simple" test for a named_scope
Mine differs slightly from the one above in that I had to remove a set of {} in the :conditions in my test to avoid an "odd number list for Hash" error. I also replace the param-binding "?" with the number I expect to send in as an argument. My test would did know what args[0] was. I got an "undefined local variable" error.
The named scope in my model:
named_scope :up_to_and_including_year, lambda{ |*args| {
:conditions => ["to_char(grad_dt1,'YYYY') <= ?", args[0]]
}}
The test:
test "named_scope :up_to_and_including_year" do expected_options = { :conditions => ["to_char(grad_dt1,'YYYY') <= ?", '2010'] } assert_equal expected_options, Sso::CourseTaken.up_to_and_including_year('2010').proxy_options end
using joins, group, having
==== Code example
named_scope :red, :joins => [:color_lis => :color], :group => "color.id", :having => ["color.xx IS NULL"]
A named_scope also responds to model class methods
for instance
class Student < ActiveRecord::Base
named_scope :sophomore, :conditions => 'year=2'
def self.eligible_to_vote
select{|s| s.age >= 18}
end
end
ss = Student.sophomore.eligible_to_vote
Anyone know the order the scopes assemble conditions?
It seems like last scope = first condition in sql. Can anyone confirm?
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.)