1
0
Fork 0
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:
Jon Leighton 2011-12-15 20:35:04 +00:00
parent 17ad71e514
commit 2b22564c4e
7 changed files with 357 additions and 342 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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