diff options
Diffstat (limited to 'activerecord/lib/active_record/scoping')
-rw-r--r-- | activerecord/lib/active_record/scoping/default.rb | 36 | ||||
-rw-r--r-- | activerecord/lib/active_record/scoping/named.rb | 54 |
2 files changed, 46 insertions, 44 deletions
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 5f05d146f2..db833fc7f1 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -1,4 +1,5 @@ require 'active_support/concern' +require 'active_support/deprecation' module ActiveRecord module Scoping @@ -30,7 +31,7 @@ module ActiveRecord # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10" # } # - # It is recommended to use the block form of unscoped because chaining + # It is recommended that you use the block form of unscoped because chaining # unscoped with <tt>scope</tt> does not work. Assuming that # <tt>published</tt> is a <tt>scope</tt>, the following two statements # are equal: the default_scope is applied on both. @@ -51,7 +52,7 @@ module ActiveRecord # the model. # # class Article < ActiveRecord::Base - # default_scope where(:published => true) + # default_scope { where(:published => true) } # end # # Article.all # => SELECT * FROM articles WHERE published = true @@ -62,12 +63,6 @@ module ActiveRecord # 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.) # @@ -75,8 +70,8 @@ module ActiveRecord # be merged together: # # class Article < ActiveRecord::Base - # default_scope where(:published => true) - # default_scope where(:rating => 'G') + # default_scope { where(:published => true) } + # default_scope { where(:rating => 'G') } # end # # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G' @@ -92,21 +87,30 @@ module ActiveRecord # # Should return a scope, you can call 'super' here etc. # end # end - def default_scope(scope = {}) + def default_scope(scope = nil) scope = Proc.new if block_given? + + if scope.is_a?(Relation) || !scope.respond_to?(:call) + ActiveSupport::Deprecation.warn( + "Calling #default_scope without a block is deprecated. For example instead " \ + "of `default_scope where(color: 'red')`, please use " \ + "`default_scope { where(color: 'red') }`. (Alternatively you can just redefine " \ + "self.default_scope.)" + ) + end + self.default_scopes = default_scopes + [scope] end def build_default_scope #:nodoc: - if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods + if !Base.is_a?(method(:default_scope).owner) + # The user has defined their own default scope method, so call that 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) + if !scope.is_a?(Relation) && scope.respond_to?(:call) + default_scope.merge(unscoped { scope.call }) else default_scope.merge(scope) end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 0edc3f1dcc..2af476c1ba 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -3,6 +3,7 @@ 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' +require 'active_support/deprecation' module ActiveRecord # = Active Record Named \Scopes @@ -28,17 +29,16 @@ module ActiveRecord # 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) + if current_scope + scope = current_scope.clone else - if current_scope - current_scope.clone - else - scope = relation.clone - scope.default_scoped = true - scope - end + scope = relation + scope.default_scoped = true + scope end + + scope.merge!(options) if options + scope end ## @@ -48,7 +48,7 @@ module ActiveRecord if current_scope current_scope.scope_for_create else - scope = relation.clone + scope = relation scope.default_scoped = true scope.scope_for_create end @@ -171,30 +171,28 @@ module ActiveRecord # 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? + def scope(name, body, &block) + extension = Module.new(&block) if block - scope_proc = lambda do |*args| - options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options - options = scoped.apply_finder_options(options) if options.is_a?(Hash) + # Check body.is_a?(Relation) to prevent the relation actually being + # loaded by respond_to? + if body.is_a?(Relation) || !body.respond_to?(:call) + ActiveSupport::Deprecation.warn( + "Using #scope without passing a callable object is deprecated. For " \ + "example `scope :red, where(color: 'red')` should be changed to " \ + "`scope :red, -> { where(color: 'red') }`. There are numerous gotchas " \ + "in the former usage and it makes the implementation more complicated " \ + "and buggy. (If you prefer, you can just define a class method named " \ + "`self.red`.)" + ) + end + singleton_class.send(:define_method, name) do |*args| + options = body.respond_to?(:call) ? unscoped { body.call(*args) } : body 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 |