From c86a32d7451c5d901620ac58630460915292f88b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 11 May 2012 17:19:30 +0100 Subject: CollectionProxy < Relation This helps bring the interfaces of CollectionProxy and Relation closer together, and reduces the delegation backflips we need to perform. For example, first_or_create is defined thus: class ActiveRecord::Relation def first_or_create(...) first || create(...) end end If CollectionProxy < Relation, then post.comments.first_or_create will hit the association's #create method which will actually add the new record to the association, just as post.comments.create would. With the previous delegation, post.comments.first_or_create expands to post.comments.scoped.first_or_create, where post.comments.scoped has no knowledge of the association. --- .../associations/collection_association.rb | 8 +---- .../active_record/associations/collection_proxy.rb | 37 +++++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) (limited to 'activerecord/lib/active_record/associations') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 14aa557b6c..00321ec860 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -16,12 +16,6 @@ module ActiveRecord # If you need to work on all current children, new and existing records, # +load_target+ and the +loaded+ flag are your friends. class CollectionAssociation < Association #:nodoc: - attr_reader :proxy - - def initialize(owner, reflection) - super - @proxy = CollectionProxy.new(self) - end # Implements the reader method, e.g. foo.items for Foo.has_many :items def reader(force_reload = false) @@ -31,7 +25,7 @@ module ActiveRecord reload end - proxy + CollectionProxy.new(self) end # Implements the writer method, e.g. foo.items= for Foo.has_many :items diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 261a829281..47ed9f58d8 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -33,14 +33,7 @@ module ActiveRecord # # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. - class CollectionProxy # :nodoc: - alias :proxy_extend :extend - - instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } - - delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, - :lock, :readonly, :having, :pluck, :to => :scoped - + class CollectionProxy < Relation # :nodoc: delegate :target, :load_target, :loaded?, :to => :@association delegate :select, :find, :first, :last, @@ -52,7 +45,8 @@ module ActiveRecord def initialize(association) @association = association - Array(association.options[:extend]).each { |ext| proxy_extend(ext) } + super association.klass, association.klass.arel_table + merge! association.scoped end alias_method :new, :build @@ -61,15 +55,24 @@ module ActiveRecord @association end + # We don't want this object to be put on the scoping stack, because + # that could create an infinite loop where we call an @association + # method, which gets the current scope, which is this object, which + # delegates to @association, and so on. + def scoping + @association.scoped.scoping { yield } + end + + def spawn + scoped + end + def scoped(options = nil) association = @association - scope = association.scoped - scope.extending! do + super.extending! do define_method(:proxy_association) { association } end - scope.merge!(options) if options - scope end def respond_to?(name, include_private = false) @@ -81,7 +84,7 @@ module ActiveRecord def method_missing(method, *args, &block) match = DynamicMatchers::Method.match(self, method) if match && match.is_a?(DynamicMatchers::Instantiator) - scoped.send(method, *args) do |r| + super do |r| proxy_association.send :set_owner_attributes, r proxy_association.send :add_to_target, r yield(r) if block_given? @@ -101,10 +104,14 @@ module ActiveRecord end else - scoped.readonly(nil).public_send(method, *args, &block) + super end end + def ==(other) + load_target == other + end + # Forwards === explicitly to the \target because the instance method # removal above doesn't catch it. Loads the \target if needed. def ===(other) -- cgit v1.2.3