aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/named_scope.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/named_scope.rb')
-rw-r--r--activerecord/lib/active_record/named_scope.rb153
1 files changed, 69 insertions, 84 deletions
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index 47b69dec62..92030e5bfd 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -9,7 +9,7 @@ module ActiveRecord
module ClassMethods
# Returns a relation if invoked without any arguments.
#
- # posts = Post.scoped
+ # 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
#
@@ -24,15 +24,9 @@ module ActiveRecord
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
def scoped(options = {}, &block)
if options.present?
- Scope.new(self, options, &block)
+ Scope.init(self, options, &block)
else
- current_scope = current_scoped_methods
-
- unless current_scope
- finder_needs_type_condition? ? active_relation.where(type_condition) : active_relation.spawn
- else
- construct_finder_arel({}, current_scoped_methods)
- end
+ current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn
end
end
@@ -44,11 +38,11 @@ module ActiveRecord
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
#
# 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]
+ # scope :red, :conditions => {:color => 'red'}
+ # scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
# end
- #
- # The above calls to <tt>named_scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
+ #
+ # 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.find(:all, :conditions => {:color => 'red'})</tt>.
#
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it resembles the association object
@@ -74,7 +68,7 @@ module ActiveRecord
# Named \scopes can also be procedural:
#
# class Shirt < ActiveRecord::Base
- # named_scope :colored, lambda { |color|
+ # scope :colored, lambda { |color|
# { :conditions => { :color => color } }
# }
# end
@@ -84,7 +78,7 @@ module ActiveRecord
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
- # named_scope :red, :conditions => {:color => 'red'} do
+ # scope :red, :conditions => {:color => 'red'} do
# def dom_id
# 'red_shirts'
# end
@@ -96,18 +90,23 @@ module ActiveRecord
# <tt>proxy_options</tt> method on the proxy itself.
#
# class Shirt < ActiveRecord::Base
- # named_scope :colored, lambda { |color|
+ # scope :colored, lambda { |color|
# { :conditions => { :color => color } }
# }
# end
#
# expected_options = { :conditions => { :colored => 'red' } }
# assert_equal expected_options, Shirt.colored('red').proxy_options
- def named_scope(name, options = {}, &block)
+ def scope(name, options = {}, &block)
name = name.to_sym
+
+ if !scopes[name] && respond_to?(name, true)
+ raise ArgumentError, "Cannot define scope :#{name} because #{self.name}.#{name} method already exists."
+ end
+
scopes[name] = lambda do |parent_scope, *args|
- Scope.new(parent_scope, case options
- when Hash
+ Scope.init(parent_scope, case options
+ when Hash, Relation
options
when Proc
options.call(*args)
@@ -119,104 +118,90 @@ module ActiveRecord
end
end
end
- end
- class Scope
- attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined
- NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set
- [].methods.each do |m|
- unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
- delegate m, :to => :proxy_found
- end
+ def named_scope(*args, &block)
+ ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
+ scope(*args, &block)
end
+ end
- delegate :scopes, :with_scope, :scoped_methods, :to => :proxy_scope
+ class Scope < Relation
+ attr_accessor :current_scoped_methods_when_defined
- def initialize(proxy_scope, options, &block)
- options ||= {}
- [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
- extend Module.new(&block) if block_given?
- unless Scope === proxy_scope
- @current_scoped_methods_when_defined = proxy_scope.send(:current_scoped_methods)
- end
- @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
- end
+ delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass
- def reload
- load_found; self
- end
+ def self.init(klass, options, &block)
+ relation = new(klass, klass.arel_table)
- def first(*args)
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
- proxy_found.first(*args)
+ scope = if options.is_a?(Hash)
+ klass.scoped.apply_finder_options(options.except(:extend))
else
- find(:first, *args)
+ options ? klass.scoped.merge(options) : klass.scoped
end
- end
- def last(*args)
- if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
- proxy_found.last(*args)
- else
- find(:last, *args)
- end
- end
+ relation = relation.merge(scope)
- def size
- @found ? @found.length : count
- end
+ Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash)
+ relation.send(:extend, Module.new(&block)) if block_given?
- def empty?
- @found ? @found.empty? : count.zero?
+ relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
+ relation
end
- def respond_to?(method, include_private = false)
- super || @proxy_scope.respond_to?(method, include_private)
+ def find(*args)
+ options = args.extract_options!
+ relation = options.present? ? apply_finder_options(options) : self
+
+ case args.first
+ when :first, :last, :all
+ relation.send(args.first)
+ else
+ options.present? ? relation.find(*args) : super
+ end
end
- def any?
- if block_given?
- proxy_found.any? { |*block_args| yield(*block_args) }
+ def first(*args)
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
+ to_a.first(*args)
else
- !empty?
+ args.first.present? ? apply_finder_options(args.first).first : super
end
end
- # Returns true if the named scope has more than 1 matching record.
- def many?
- if block_given?
- proxy_found.many? { |*block_args| yield(*block_args) }
+ def last(*args)
+ if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
+ to_a.last(*args)
else
- size > 1
+ args.first.present? ? apply_finder_options(args.first).last : super
end
end
- protected
- def proxy_found
- @found || load_found
+ def count(*args)
+ options = args.extract_options!
+ options.present? ? apply_finder_options(options).count(*args) : super
+ end
+
+ def ==(other)
+ to_a == other.to_a
end
private
+
def method_missing(method, *args, &block)
- if scopes.include?(method)
- scopes[method].call(self, *args)
- else
- with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do
- method = :new if method == :build
- if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined)
- with_scope current_scoped_methods_when_defined do
- proxy_scope.send(method, *args, &block)
- end
+ if klass.respond_to?(method)
+ with_scope(self) do
+ if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method)
+ with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) }
else
- proxy_scope.send(method, *args, &block)
+ klass.send(method, *args, &block)
end
end
+ else
+ super
end
end
- def load_found
- @found = find(:all)
- end
end
+
end
end