mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Move DefaultScope and NamedScope under Scoping
This commit is contained in:
parent
17ad71e514
commit
2b22564c4e
7 changed files with 357 additions and 342 deletions
|
@ -58,7 +58,6 @@ module ActiveRecord
|
|||
autoload :Base
|
||||
autoload :Callbacks
|
||||
autoload :CounterCache
|
||||
autoload :DefaultScope
|
||||
autoload :DynamicMatchers
|
||||
autoload :DynamicFinderMatch
|
||||
autoload :DynamicScopeMatch
|
||||
|
@ -69,7 +68,6 @@ module ActiveRecord
|
|||
autoload :Migration
|
||||
autoload :Migrator, 'active_record/migration'
|
||||
autoload :ModelSchema
|
||||
autoload :NamedScope
|
||||
autoload :NestedAttributes
|
||||
autoload :Observer
|
||||
autoload :Persistence
|
||||
|
@ -129,6 +127,15 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
module Scoping
|
||||
extend ActiveSupport::Autoload
|
||||
|
||||
eager_autoload do
|
||||
autoload :Named
|
||||
autoload :Default
|
||||
end
|
||||
end
|
||||
|
||||
autoload :TestCase
|
||||
autoload :TestFixtures, 'active_record/fixtures'
|
||||
end
|
||||
|
|
|
@ -686,7 +686,6 @@ module ActiveRecord #:nodoc:
|
|||
extend Translation
|
||||
include Inheritance
|
||||
include Scoping
|
||||
include DefaultScope
|
||||
extend DynamicMatchers
|
||||
include Sanitization
|
||||
include Integration
|
||||
|
@ -697,7 +696,7 @@ module ActiveRecord #:nodoc:
|
|||
include Locking::Optimistic, Locking::Pessimistic
|
||||
include AttributeMethods
|
||||
include Callbacks, ActiveModel::Observing, Timestamp
|
||||
include Associations, NamedScope
|
||||
include Associations
|
||||
include IdentityMap
|
||||
include ActiveModel::SecurePassword
|
||||
include Explain
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
require 'active_support/concern'
|
||||
|
||||
module ActiveRecord
|
||||
module DefaultScope
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Stores the default scope for the class
|
||||
class_attribute :default_scopes, :instance_writer => false
|
||||
self.default_scopes = []
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns a scope for this class without taking into account the default_scope.
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# def self.default_scope
|
||||
# where :published => true
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
||||
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
||||
#
|
||||
# This method also accepts a block meaning that all queries inside the block will
|
||||
# not use the default_scope:
|
||||
#
|
||||
# Post.unscoped {
|
||||
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
||||
# }
|
||||
#
|
||||
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
|
||||
# does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
|
||||
#
|
||||
# Post.unscoped.published
|
||||
# Post.published
|
||||
def unscoped #:nodoc:
|
||||
block_given? ? relation.scoping { yield } : relation
|
||||
end
|
||||
|
||||
def before_remove_const #:nodoc:
|
||||
self.current_scope = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Use this macro in your model to set a default scope for all operations on
|
||||
# the model.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope where(:published => true)
|
||||
# end
|
||||
#
|
||||
# Article.all # => SELECT * FROM articles WHERE published = true
|
||||
#
|
||||
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
||||
# applied while updating a record.
|
||||
#
|
||||
# Article.new.published # => true
|
||||
# Article.create.published # => true
|
||||
#
|
||||
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope { where(:published_at => Time.now - 1.week) }
|
||||
# end
|
||||
#
|
||||
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
||||
# macro, and it will be called when building the default scope.)
|
||||
#
|
||||
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
||||
# be merged together:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope where(:published => true)
|
||||
# default_scope where(:rating => 'G')
|
||||
# end
|
||||
#
|
||||
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
||||
#
|
||||
# This is also the case with inheritance and module includes where the parent or module
|
||||
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
||||
#
|
||||
# If you need to do more complex things with a default scope, you can alternatively
|
||||
# define it as a class method:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# def self.default_scope
|
||||
# # Should return a scope, you can call 'super' here etc.
|
||||
# end
|
||||
# end
|
||||
def default_scope(scope = {})
|
||||
scope = Proc.new if block_given?
|
||||
self.default_scopes = default_scopes + [scope]
|
||||
end
|
||||
|
||||
def build_default_scope #:nodoc:
|
||||
if method(:default_scope).owner != DefaultScope::ClassMethods
|
||||
evaluate_default_scope { default_scope }
|
||||
elsif default_scopes.any?
|
||||
evaluate_default_scope do
|
||||
default_scopes.inject(relation) do |default_scope, scope|
|
||||
if scope.is_a?(Hash)
|
||||
default_scope.apply_finder_options(scope)
|
||||
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
||||
default_scope.merge(scope.call)
|
||||
else
|
||||
default_scope.merge(scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ignore_default_scope? #:nodoc:
|
||||
Thread.current["#{self}_ignore_default_scope"]
|
||||
end
|
||||
|
||||
def ignore_default_scope=(ignore) #:nodoc:
|
||||
Thread.current["#{self}_ignore_default_scope"] = ignore
|
||||
end
|
||||
|
||||
# The ignore_default_scope flag is used to prevent an infinite recursion situation where
|
||||
# a default scope references a scope which has a default scope which references a scope...
|
||||
def evaluate_default_scope
|
||||
return if ignore_default_scope?
|
||||
|
||||
begin
|
||||
self.ignore_default_scope = true
|
||||
yield
|
||||
ensure
|
||||
self.ignore_default_scope = false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,200 +0,0 @@
|
|||
require 'active_support/core_ext/array'
|
||||
require 'active_support/core_ext/hash/except'
|
||||
require 'active_support/core_ext/kernel/singleton_class'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
module ActiveRecord
|
||||
# = Active Record Named \Scopes
|
||||
module NamedScope
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Returns an anonymous \scope.
|
||||
#
|
||||
# posts = Post.scoped
|
||||
# posts.size # Fires "select count(*) from posts" and returns the count
|
||||
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
||||
#
|
||||
# fruits = Fruit.scoped
|
||||
# fruits = fruits.where(:color => 'red') if options[:red_only]
|
||||
# fruits = fruits.limit(10) if limited?
|
||||
#
|
||||
# Anonymous \scopes tend to be useful when procedurally generating complex
|
||||
# queries, where passing intermediate values (\scopes) around as first-class
|
||||
# objects is convenient.
|
||||
#
|
||||
# You can define a \scope that applies to all finders using
|
||||
# ActiveRecord::Base.default_scope.
|
||||
def scoped(options = nil)
|
||||
if options
|
||||
scoped.apply_finder_options(options)
|
||||
else
|
||||
if current_scope
|
||||
current_scope.clone
|
||||
else
|
||||
scope = relation.clone
|
||||
scope.default_scoped = true
|
||||
scope
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Collects attributes from scopes that should be applied when creating
|
||||
# an AR instance for the particular class this is called on.
|
||||
def scope_attributes # :nodoc:
|
||||
if current_scope
|
||||
current_scope.scope_for_create
|
||||
else
|
||||
scope = relation.clone
|
||||
scope.default_scoped = true
|
||||
scope.scope_for_create
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Are there default attributes associated with this scope?
|
||||
def scope_attributes? # :nodoc:
|
||||
current_scope || default_scopes.any?
|
||||
end
|
||||
|
||||
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
|
||||
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :red, where(:color => 'red')
|
||||
# scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
|
||||
# end
|
||||
#
|
||||
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
|
||||
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
|
||||
#
|
||||
# Note that this is simply 'syntactic sugar' for defining an actual class method:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# def self.red
|
||||
# where(:color => 'red')
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
|
||||
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
|
||||
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
|
||||
# Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
|
||||
# <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
|
||||
# all behave as if Shirt.red really was an Array.
|
||||
#
|
||||
# These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
|
||||
# all shirts that are both red and dry clean only.
|
||||
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
|
||||
# returns the number of garments for which these criteria obtain. Similarly with
|
||||
# <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
||||
#
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
|
||||
# the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# has_many :shirts
|
||||
# end
|
||||
#
|
||||
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
|
||||
# only shirts.
|
||||
#
|
||||
# Named \scopes can also be procedural:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :colored, lambda { |color| where(:color => color) }
|
||||
# end
|
||||
#
|
||||
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
||||
#
|
||||
# On Ruby 1.9 you can use the 'stabby lambda' syntax:
|
||||
#
|
||||
# scope :colored, ->(color) { where(:color => color) }
|
||||
#
|
||||
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
|
||||
# when they are used. For example, the following would be incorrect:
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# scope :recent, where('published_at >= ?', Time.now - 1.week)
|
||||
# end
|
||||
#
|
||||
# The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
|
||||
# class was defined, and so the resultant SQL query would always be the same. The correct
|
||||
# way to do this would be via a lambda, which will re-evaluate the scope each time
|
||||
# it is called:
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
|
||||
# end
|
||||
#
|
||||
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :red, where(:color => 'red') do
|
||||
# def dom_id
|
||||
# 'red_shirts'
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Scopes can also be used while creating/building a record.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# scope :published, where(:published => true)
|
||||
# end
|
||||
#
|
||||
# Article.published.new.published # => true
|
||||
# Article.published.create.published # => true
|
||||
#
|
||||
# Class methods on your model are automatically available
|
||||
# on scopes. Assuming the following setup:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# scope :published, where(:published => true)
|
||||
# scope :featured, where(:featured => true)
|
||||
#
|
||||
# def self.latest_article
|
||||
# order('published_at desc').first
|
||||
# end
|
||||
#
|
||||
# def self.titles
|
||||
# map(&:title)
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# We are able to call the methods like this:
|
||||
#
|
||||
# Article.published.featured.latest_article
|
||||
# Article.featured.titles
|
||||
|
||||
def scope(name, scope_options = {})
|
||||
name = name.to_sym
|
||||
valid_scope_name?(name)
|
||||
extension = Module.new(&Proc.new) if block_given?
|
||||
|
||||
scope_proc = lambda do |*args|
|
||||
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
|
||||
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
|
||||
|
||||
relation = scoped.merge(options)
|
||||
|
||||
extension ? relation.extending(extension) : relation
|
||||
end
|
||||
|
||||
singleton_class.send(:redefine_method, name, &scope_proc)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def valid_scope_name?(name)
|
||||
if respond_to?(name, true)
|
||||
logger.warn "Creating scope :#{name}. " \
|
||||
"Overwriting existing method #{self.name}.#{name}."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,6 +4,11 @@ module ActiveRecord
|
|||
module Scoping
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include Default
|
||||
include Named
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
|
||||
# <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
||||
|
|
140
activerecord/lib/active_record/scoping/default.rb
Normal file
140
activerecord/lib/active_record/scoping/default.rb
Normal file
|
@ -0,0 +1,140 @@
|
|||
require 'active_support/concern'
|
||||
|
||||
module ActiveRecord
|
||||
module Scoping
|
||||
module Default
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
# Stores the default scope for the class
|
||||
class_attribute :default_scopes, :instance_writer => false
|
||||
self.default_scopes = []
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns a scope for this class without taking into account the default_scope.
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# def self.default_scope
|
||||
# where :published => true
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
||||
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
||||
#
|
||||
# This method also accepts a block meaning that all queries inside the block will
|
||||
# not use the default_scope:
|
||||
#
|
||||
# Post.unscoped {
|
||||
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
||||
# }
|
||||
#
|
||||
# It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
|
||||
# does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
|
||||
#
|
||||
# Post.unscoped.published
|
||||
# Post.published
|
||||
def unscoped #:nodoc:
|
||||
block_given? ? relation.scoping { yield } : relation
|
||||
end
|
||||
|
||||
def before_remove_const #:nodoc:
|
||||
self.current_scope = nil
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Use this macro in your model to set a default scope for all operations on
|
||||
# the model.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope where(:published => true)
|
||||
# end
|
||||
#
|
||||
# Article.all # => SELECT * FROM articles WHERE published = true
|
||||
#
|
||||
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
||||
# applied while updating a record.
|
||||
#
|
||||
# Article.new.published # => true
|
||||
# Article.create.published # => true
|
||||
#
|
||||
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope { where(:published_at => Time.now - 1.week) }
|
||||
# end
|
||||
#
|
||||
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
||||
# macro, and it will be called when building the default scope.)
|
||||
#
|
||||
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
||||
# be merged together:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# default_scope where(:published => true)
|
||||
# default_scope where(:rating => 'G')
|
||||
# end
|
||||
#
|
||||
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
||||
#
|
||||
# This is also the case with inheritance and module includes where the parent or module
|
||||
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
||||
#
|
||||
# If you need to do more complex things with a default scope, you can alternatively
|
||||
# define it as a class method:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# def self.default_scope
|
||||
# # Should return a scope, you can call 'super' here etc.
|
||||
# end
|
||||
# end
|
||||
def default_scope(scope = {})
|
||||
scope = Proc.new if block_given?
|
||||
self.default_scopes = default_scopes + [scope]
|
||||
end
|
||||
|
||||
def build_default_scope #:nodoc:
|
||||
if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods
|
||||
evaluate_default_scope { default_scope }
|
||||
elsif default_scopes.any?
|
||||
evaluate_default_scope do
|
||||
default_scopes.inject(relation) do |default_scope, scope|
|
||||
if scope.is_a?(Hash)
|
||||
default_scope.apply_finder_options(scope)
|
||||
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
||||
default_scope.merge(scope.call)
|
||||
else
|
||||
default_scope.merge(scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def ignore_default_scope? #:nodoc:
|
||||
Thread.current["#{self}_ignore_default_scope"]
|
||||
end
|
||||
|
||||
def ignore_default_scope=(ignore) #:nodoc:
|
||||
Thread.current["#{self}_ignore_default_scope"] = ignore
|
||||
end
|
||||
|
||||
# The ignore_default_scope flag is used to prevent an infinite recursion situation where
|
||||
# a default scope references a scope which has a default scope which references a scope...
|
||||
def evaluate_default_scope
|
||||
return if ignore_default_scope?
|
||||
|
||||
begin
|
||||
self.ignore_default_scope = true
|
||||
yield
|
||||
ensure
|
||||
self.ignore_default_scope = false
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
202
activerecord/lib/active_record/scoping/named.rb
Normal file
202
activerecord/lib/active_record/scoping/named.rb
Normal file
|
@ -0,0 +1,202 @@
|
|||
require 'active_support/core_ext/array'
|
||||
require 'active_support/core_ext/hash/except'
|
||||
require 'active_support/core_ext/kernel/singleton_class'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
module ActiveRecord
|
||||
# = Active Record Named \Scopes
|
||||
module Scoping
|
||||
module Named
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ClassMethods
|
||||
# Returns an anonymous \scope.
|
||||
#
|
||||
# posts = Post.scoped
|
||||
# posts.size # Fires "select count(*) from posts" and returns the count
|
||||
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
||||
#
|
||||
# fruits = Fruit.scoped
|
||||
# fruits = fruits.where(:color => 'red') if options[:red_only]
|
||||
# fruits = fruits.limit(10) if limited?
|
||||
#
|
||||
# Anonymous \scopes tend to be useful when procedurally generating complex
|
||||
# queries, where passing intermediate values (\scopes) around as first-class
|
||||
# objects is convenient.
|
||||
#
|
||||
# You can define a \scope that applies to all finders using
|
||||
# ActiveRecord::Base.default_scope.
|
||||
def scoped(options = nil)
|
||||
if options
|
||||
scoped.apply_finder_options(options)
|
||||
else
|
||||
if current_scope
|
||||
current_scope.clone
|
||||
else
|
||||
scope = relation.clone
|
||||
scope.default_scoped = true
|
||||
scope
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Collects attributes from scopes that should be applied when creating
|
||||
# an AR instance for the particular class this is called on.
|
||||
def scope_attributes # :nodoc:
|
||||
if current_scope
|
||||
current_scope.scope_for_create
|
||||
else
|
||||
scope = relation.clone
|
||||
scope.default_scoped = true
|
||||
scope.scope_for_create
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Are there default attributes associated with this scope?
|
||||
def scope_attributes? # :nodoc:
|
||||
current_scope || default_scopes.any?
|
||||
end
|
||||
|
||||
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
|
||||
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :red, where(:color => 'red')
|
||||
# scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
|
||||
# end
|
||||
#
|
||||
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
|
||||
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
|
||||
#
|
||||
# Note that this is simply 'syntactic sugar' for defining an actual class method:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# def self.red
|
||||
# where(:color => 'red')
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
|
||||
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
|
||||
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
|
||||
# Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
|
||||
# <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
|
||||
# all behave as if Shirt.red really was an Array.
|
||||
#
|
||||
# These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
|
||||
# all shirts that are both red and dry clean only.
|
||||
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
|
||||
# returns the number of garments for which these criteria obtain. Similarly with
|
||||
# <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
||||
#
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendant upon which
|
||||
# the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# has_many :shirts
|
||||
# end
|
||||
#
|
||||
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
|
||||
# only shirts.
|
||||
#
|
||||
# Named \scopes can also be procedural:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :colored, lambda { |color| where(:color => color) }
|
||||
# end
|
||||
#
|
||||
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
||||
#
|
||||
# On Ruby 1.9 you can use the 'stabby lambda' syntax:
|
||||
#
|
||||
# scope :colored, ->(color) { where(:color => color) }
|
||||
#
|
||||
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
|
||||
# when they are used. For example, the following would be incorrect:
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# scope :recent, where('published_at >= ?', Time.now - 1.week)
|
||||
# end
|
||||
#
|
||||
# The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
|
||||
# class was defined, and so the resultant SQL query would always be the same. The correct
|
||||
# way to do this would be via a lambda, which will re-evaluate the scope each time
|
||||
# it is called:
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
|
||||
# end
|
||||
#
|
||||
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
||||
#
|
||||
# class Shirt < ActiveRecord::Base
|
||||
# scope :red, where(:color => 'red') do
|
||||
# def dom_id
|
||||
# 'red_shirts'
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Scopes can also be used while creating/building a record.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# scope :published, where(:published => true)
|
||||
# end
|
||||
#
|
||||
# Article.published.new.published # => true
|
||||
# Article.published.create.published # => true
|
||||
#
|
||||
# Class methods on your model are automatically available
|
||||
# on scopes. Assuming the following setup:
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# scope :published, where(:published => true)
|
||||
# scope :featured, where(:featured => true)
|
||||
#
|
||||
# def self.latest_article
|
||||
# order('published_at desc').first
|
||||
# end
|
||||
#
|
||||
# def self.titles
|
||||
# map(&:title)
|
||||
# end
|
||||
#
|
||||
# end
|
||||
#
|
||||
# We are able to call the methods like this:
|
||||
#
|
||||
# Article.published.featured.latest_article
|
||||
# Article.featured.titles
|
||||
|
||||
def scope(name, scope_options = {})
|
||||
name = name.to_sym
|
||||
valid_scope_name?(name)
|
||||
extension = Module.new(&Proc.new) if block_given?
|
||||
|
||||
scope_proc = lambda do |*args|
|
||||
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
|
||||
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
|
||||
|
||||
relation = scoped.merge(options)
|
||||
|
||||
extension ? relation.extending(extension) : relation
|
||||
end
|
||||
|
||||
singleton_class.send(:redefine_method, name, &scope_proc)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def valid_scope_name?(name)
|
||||
if respond_to?(name, true)
|
||||
logger.warn "Creating scope :#{name}. " \
|
||||
"Overwriting existing method #{self.name}.#{name}."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue