From 88de6b2de2606e141483ff90323c5f3ec0cfb298 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Mon, 18 Jan 2010 04:24:24 +0530 Subject: [PATCH] Inherit named scope class Scope from Relation --- .../associations/association_collection.rb | 2 - activerecord/lib/active_record/named_scope.rb | 118 +++++++----------- activerecord/lib/active_record/relation.rb | 4 +- .../relation/predicate_builder.rb | 2 +- .../active_record/relation/spawn_methods.rb | 6 +- activerecord/test/cases/named_scope_test.rb | 17 +-- 6 files changed, 55 insertions(+), 94 deletions(-) diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 64dd5cf629..e9402d3547 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -403,8 +403,6 @@ def method_missing(method, *args) else super end - elsif @reflection.klass.scopes.include?(method) - @reflection.klass.scopes[method].call(self, *args) else with_scope(construct_scope) do if block_given? diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 9e65fb4ca5..16fde1ffb8 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -24,7 +24,7 @@ module ClassMethods # 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_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn end @@ -105,7 +105,7 @@ def named_scope(name, options = {}, &block) end scopes[name] = lambda do |parent_scope, *args| - Scope.new(parent_scope, case options + Scope.init(parent_scope, case options when Hash, Relation options when Proc @@ -120,117 +120,83 @@ def named_scope(name, options = {}, &block) end end - class Scope - attr_reader :klass, :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 - end + class Scope < Relation + attr_accessor :current_scoped_methods_when_defined delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass - delegate :new, :build, :all, :to => :relation - def initialize(klass, options, &block) - extend Module.new(&block) if block_given? + def self.init(klass, options, &block) + relation = new(klass, klass.arel_table) - options ||= {} - if options.is_a?(Hash) - Array.wrap(options[:extend]).each {|extension| extend extension } - @proxy_options = options.except(:extend) + scope = if options.is_a?(Hash) + klass.scoped.apply_finder_options(options.except(:extend)) else - @proxy_options = options + options ? klass.scoped.merge(options) : klass.scoped end - unless Scope === klass - @current_scoped_methods_when_defined = klass.send(:current_scoped_methods) - end + relation = relation.merge(scope) - @klass = klass - 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 reload - load_found; self + relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods) + relation end - def first(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.first(*args) - else - find(:first, *args) - end - end + def find(*args) + options = args.extract_options! + relation = options.present? ? apply_finder_options(options) : self - def last(*args) - if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) - proxy_found.last(*args) + case args.first + when :first, :last, :all + relation.send(args.first) else - find(:last, *args) + options.present? ? relation.find(*args) : super end end - def size - @found ? @found.length : count - end - - def empty? - @found ? @found.empty? : count.zero? - end - - def respond_to?(method, include_private = false) - super || @klass.respond_to?(method, include_private) - 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 - def relation - @relation ||= begin - if proxy_options.is_a?(Hash) - scoped.apply_finder_options(proxy_options) - else - scoped.merge(proxy_options) - end - end + def count(*args) + options = args.extract_options! + options.present? ? apply_finder_options(options).count(*args) : super end - def proxy_found - @found || load_found + def ==(other) + to_a == other.to_a end private def method_missing(method, *args, &block) - with_scope(relation, :reverse_merge) 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 do + 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 klass.send(method, *args, &block) end - else - klass.send(method, *args, &block) end + else + super end end - def load_found - @found = find(:all) - end - end + end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fc429486e4..1d6fced952 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -7,7 +7,7 @@ class Relation include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods - delegate :length, :collect, :map, :each, :all?, :to => :to_a + delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a attr_reader :table, :klass @@ -175,6 +175,8 @@ def method_missing(method, *args, &block) end end + private + def with_create_scope @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 6b7d941350..03f194c462 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -24,7 +24,7 @@ def build_from_hash(attributes, default_table) case value when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope - attribute.in(value) + attribute.in(value.to_a) when Range # TODO : Arel should handle ranges with excluded end. if value.exclude_end? diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 2979f4b82d..4ac9e50f5a 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,7 +1,7 @@ module ActiveRecord module SpawnMethods def spawn(arel_table = self.table) - relation = Relation.new(@klass, arel_table) + relation = self.class.new(@klass, arel_table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) @@ -64,7 +64,7 @@ def merge(r) alias :& :merge def except(*skips) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) @@ -78,7 +78,7 @@ def except(*skips) end def only(*onlies) - result = Relation.new(@klass, table) + result = self.class.new(@klass, table) onlies.each do |only| if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 09a657791e..ce1ac845cd 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -31,7 +31,7 @@ def test_found_items_are_cached def test_reload_expires_cache_of_found_items all_posts = Topic.base - all_posts.inspect + all_posts.all new_post = Topic.create! assert !all_posts.include?(new_post) @@ -48,14 +48,14 @@ def test_delegates_finds_and_calculations_to_the_base_class end def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy - assert Topic.approved.respond_to?(:proxy_found) + assert Topic.approved.respond_to?(:limit) assert Topic.approved.respond_to?(:count) assert Topic.approved.respond_to?(:length) end def test_respond_to_respects_include_private_parameter - assert !Topic.approved.respond_to?(:load_found) - assert Topic.approved.respond_to?(:load_found, true) + assert !Topic.approved.respond_to?(:with_create_scope) + assert Topic.approved.respond_to?(:with_create_scope, true) end def test_subclasses_inherit_scopes @@ -155,7 +155,7 @@ def test_named_scopes_honor_current_scopes_from_when_defined assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) # Oracle sometimes sorts differently if WHERE condition is changed - assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) + assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id) assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) end @@ -171,11 +171,6 @@ def test_active_records_have_scope_named__scoped__ assert_equal Topic.find(:all, scope), Topic.scoped(scope) end - def test_proxy_options - expected_proxy_options = { :conditions => { :approved => true } } - assert_equal expected_proxy_options, Topic.approved.proxy_options - end - def test_first_and_last_should_support_find_options assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title') assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title') @@ -297,7 +292,7 @@ def test_should_build_with_proxy_options_chained end def test_find_all_should_behave_like_select - assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved) + assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved) end def test_rand_should_select_a_random_object_from_proxy -- GitLab