From 2d9626fc74c2d57f90c856c37e5967bbe6651bd8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 22 Dec 2010 20:57:41 +0000 Subject: Improved strategy for updating a belongs_to association when the foreign key changes. Rather than resetting each affected association when the foreign key changes, we should lazily check for 'staleness' (where fk does not match target id) when the association is accessed. --- .../lib/active_record/associations/association_proxy.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 252ff7e7ea..f4eceeed8c 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -130,6 +130,16 @@ module ActiveRecord @loaded = true end + # The target is stale if the target no longer points to the record(s) that the + # relevant foreign_key(s) refers to. If stale, the association accessor method + # on the owner will reload the target. It's up to subclasses to implement this + # method if relevant. + # + # Note that if the target has not been loaded, it is not considered stale. + def stale_target? + false + end + # Returns the target of this proxy, same as +proxy_target+. def target @target -- cgit v1.2.3 From e8ada11aac28f0850f0e485acacf34e7eb81aa19 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 24 Dec 2010 00:29:04 +0000 Subject: Associations: DRY up the code which is generating conditions, and make it all use arel rather than SQL strings --- .../associations/association_proxy.rb | 45 +++++++++++++++++----- 1 file changed, 36 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index f4eceeed8c..f51275d86d 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -181,18 +181,41 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Assigns the ID of the owner to the corresponding foreign key in +record+. - # If the association is polymorphic the type of the owner is also set. - def set_belongs_to_association_for(record) - if @reflection.options[:as] - record["#{@reflection.options[:as]}_id"] = @owner.id if @owner.persisted? - record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s + # Sets the owner attributes on the given record + # Note: does not really make sense for belongs_to associations, but this method is not + # used by belongs_to + def set_owner_attributes(record) + if @owner.persisted? + construct_owner_attributes.each { |key, value| record[key] = value } + end + end + + # Returns a has linking the owner to the association represented by the reflection + def construct_owner_attributes(reflection = @reflection) + attributes = {} + if reflection.macro == :belongs_to + attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name) else - if @owner.persisted? - primary_key = @reflection.options[:primary_key] || :id - record[@reflection.primary_key_name] = @owner.send(primary_key) + attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key) + + if reflection.options[:as] + attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name end end + attributes + end + + # Builds an array of arel nodes from the owner attributes hash + def construct_owner_conditions(table = aliased_table, reflection = @reflection) + construct_owner_attributes(reflection).map do |attr, value| + table[attr].eq(value) + end + end + + def construct_conditions + conditions = construct_owner_conditions + conditions << Arel.sql(sql_conditions) if sql_conditions + aliased_table.create_and(conditions) end # Merges into +options+ the ones coming from the reflection. @@ -232,6 +255,10 @@ module ActiveRecord {} end + def aliased_table + @reflection.klass.arel_table + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) -- cgit v1.2.3 From 0c272471fe815c121a83d959468b2f1b6f8aaba8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 26 Dec 2010 19:04:26 +0000 Subject: Remove AssociationProxy#dependent? - it's badly named and only used in one place --- activerecord/lib/active_record/associations/association_proxy.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index f51275d86d..c7b171b41d 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -167,11 +167,6 @@ module ActiveRecord end protected - # Does the association have a :dependent option? - def dependent? - @reflection.options[:dependent] - end - def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) end -- cgit v1.2.3 From b0498372a10bda006350af42708a5588ab28ffcb Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 26 Dec 2010 19:37:35 +0000 Subject: Add a HasAssociation module for common code for has_* associations --- .../associations/association_proxy.rb | 59 ++-------------------- 1 file changed, 5 insertions(+), 54 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index c7b171b41d..b60aa33c98 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -4,17 +4,17 @@ module ActiveRecord module Associations # = Active Record Associations # - # This is the root class of all association proxies: + # This is the root class of all association proxies ('+ Foo' signifies an included module Foo): # # AssociationProxy # BelongsToAssociation - # HasOneAssociation # BelongsToPolymorphicAssociation - # AssociationCollection + # AssociationCollection + HasAssociation # HasAndBelongsToManyAssociation # HasManyAssociation - # HasManyThroughAssociation - # HasOneThroughAssociation + # HasManyThroughAssociation + ThroughAssociation + # HasOneAssociation + HasAssociation + # HasOneThroughAssociation + ThroughAssociation # # Association proxies in Active Record are middlemen between the object that # holds the association, known as the @owner, and the actual associated @@ -176,43 +176,6 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Sets the owner attributes on the given record - # Note: does not really make sense for belongs_to associations, but this method is not - # used by belongs_to - def set_owner_attributes(record) - if @owner.persisted? - construct_owner_attributes.each { |key, value| record[key] = value } - end - end - - # Returns a has linking the owner to the association represented by the reflection - def construct_owner_attributes(reflection = @reflection) - attributes = {} - if reflection.macro == :belongs_to - attributes[reflection.association_primary_key] = @owner.send(reflection.primary_key_name) - else - attributes[reflection.primary_key_name] = @owner.send(reflection.active_record_primary_key) - - if reflection.options[:as] - attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name - end - end - attributes - end - - # Builds an array of arel nodes from the owner attributes hash - def construct_owner_conditions(table = aliased_table, reflection = @reflection) - construct_owner_attributes(reflection).map do |attr, value| - table[attr].eq(value) - end - end - - def construct_conditions - conditions = construct_owner_conditions - conditions << Arel.sql(sql_conditions) if sql_conditions - aliased_table.create_and(conditions) - end - # Merges into +options+ the ones coming from the reflection. def merge_options_from_reflection!(options) options.reverse_merge!( @@ -312,18 +275,6 @@ module ActiveRecord end end - if RUBY_VERSION < '1.9.2' - # Array#flatten has problems with recursive arrays before Ruby 1.9.2. - # Going one level deeper solves the majority of the problems. - def flatten_deeper(array) - array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten - end - else - def flatten_deeper(array) - array.flatten - end - end - # Returns the ID of the owner, quoted if needed. def owner_quoted_id @owner.quoted_id -- cgit v1.2.3 From 9f5c18ce075179cfc73a00cba9a19d69aaf5274c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 26 Dec 2010 21:37:25 +0000 Subject: Refactor we_can_set_the_inverse_on_this? to use a less bizarre name amongst other things --- .../associations/association_proxy.rb | 24 ++++++++++++++-------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index b60aa33c98..6720d83199 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -217,6 +217,13 @@ module ActiveRecord @reflection.klass.arel_table end + # Set the inverse association, if possible + def set_inverse_instance(record) + if record && invertible_for?(record) + record.send("set_#{inverse_reflection_for(record).name}_target", @owner) + end + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) @@ -280,17 +287,16 @@ module ActiveRecord @owner.quoted_id end - def set_inverse_instance(record, instance) - return if record.nil? || !we_can_set_the_inverse_on_this?(record) - inverse_relationship = @reflection.inverse_of - unless inverse_relationship.nil? - record.send(:"set_#{inverse_relationship.name}_target", instance) - end + # Can be redefined by subclasses, notably polymorphic belongs_to + # The record parameter is necessary to support polymorphic inverses as we must check for + # the association in the specific class of the record. + def inverse_reflection_for(record) + @reflection.inverse_of end - # Override in subclasses - def we_can_set_the_inverse_on_this?(record) - false + # Is this association invertible? Can be redefined by subclasses. + def invertible_for?(record) + inverse_reflection_for(record) end end end -- cgit v1.2.3 From 6db908515919a22c475bec2dd0ddc157e7933f3d Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 30 Dec 2010 20:43:55 +0000 Subject: And owner_quoted_id can go too --- activerecord/lib/active_record/associations/association_proxy.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 6720d83199..97eab8a793 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -282,11 +282,6 @@ module ActiveRecord end end - # Returns the ID of the owner, quoted if needed. - def owner_quoted_id - @owner.quoted_id - end - # Can be redefined by subclasses, notably polymorphic belongs_to # The record parameter is necessary to support polymorphic inverses as we must check for # the association in the specific class of the record. -- cgit v1.2.3 From bea4065d3c8c8f845ddda45b3ec98e3fb308d913 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 31 Dec 2010 18:08:44 +0000 Subject: Refactor BelongsToAssociation to allow BelongsToPolymorphicAssociation to inherit from it --- .../associations/association_proxy.rb | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 97eab8a793..7e68241a2c 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -8,7 +8,7 @@ module ActiveRecord # # AssociationProxy # BelongsToAssociation - # BelongsToPolymorphicAssociation + # BelongsToPolymorphicAssociation # AssociationCollection + HasAssociation # HasAndBelongsToManyAssociation # HasManyAssociation @@ -116,6 +116,7 @@ module ActiveRecord # Reloads the \target and returns +self+ on success. def reload reset + construct_scope load_target self unless @target.nil? end @@ -166,6 +167,10 @@ module ActiveRecord end end + def scoped + with_scope(@scope) { target_klass.scoped } + end + protected def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) @@ -192,15 +197,19 @@ module ActiveRecord # Forwards +with_scope+ to the reflection. def with_scope(*args, &block) - @reflection.klass.send :with_scope, *args, &block + target_klass.send :with_scope, *args, &block end # Construct the scope used for find/create queries on the target def construct_scope - @scope = { - :find => construct_find_scope, - :create => construct_create_scope - } + if target_klass + @scope = { + :find => construct_find_scope, + :create => construct_create_scope + } + else + @scope = nil + end end # Implemented by subclasses @@ -214,7 +223,7 @@ module ActiveRecord end def aliased_table - @reflection.klass.arel_table + target_klass.arel_table end # Set the inverse association, if possible @@ -224,6 +233,12 @@ module ActiveRecord end end + # This class of the target. belongs_to polymorphic overrides this to look at the + # polymorphic_type field on the owner. + def target_klass + @reflection.klass + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) @@ -254,7 +269,7 @@ module ActiveRecord def load_target return nil unless defined?(@loaded) - if !loaded? && (!@owner.new_record? || foreign_key_present) + if !loaded? && (!@owner.new_record? || foreign_key_present) && @scope @target = find_target end -- cgit v1.2.3 From a0be389d39b790e0625339251d2674b8250b16b1 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 2 Jan 2011 14:28:53 +0000 Subject: Allow assignment on has_one :through where the owner is a new record [#5137 state:resolved] This required changing the code to keep the association proxy for a belongs_to around, despite its target being nil. Which in turn required various changes to the way that stale target checking is handled, in order to support various edge cases (loaded target is nil then foreign key added, foreign key is changed and then changed back, etc). A side effect is that the code is nicer and more succinct. Note that I am removing test_no_unexpected_aliasing since that is basically checking that the proxy for a belongs_to *does* change, which is the exact opposite of the intention of this commit. Also adding various tests for various edge cases and related things. Phew, long commit message! --- .../associations/association_proxy.rb | 31 +++++++++++++++------- 1 file changed, 22 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 7e68241a2c..65fef81d64 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -128,17 +128,18 @@ module ActiveRecord # Asserts the \target has been loaded setting the \loaded flag to +true+. def loaded - @loaded = true + @loaded = true + @stale_state = stale_state end # The target is stale if the target no longer points to the record(s) that the # relevant foreign_key(s) refers to. If stale, the association accessor method - # on the owner will reload the target. It's up to subclasses to implement this - # method if relevant. + # on the owner will reload the target. It's up to subclasses to implement the + # state_state method if relevant. # # Note that if the target has not been loaded, it is not considered stale. def stale_target? - false + loaded? && @stale_state != stale_state end # Returns the target of this proxy, same as +proxy_target+. @@ -273,16 +274,19 @@ module ActiveRecord @target = find_target end - @loaded = true + loaded @target rescue ActiveRecord::RecordNotFound reset end - # Can be overwritten by associations that might have the foreign key - # available for an association without having the object itself (and - # still being a new record). Currently, only +belongs_to+ presents - # this scenario (both vanilla and polymorphic). + # Should be true if there is a foreign key present on the @owner which + # references the target. This is used to determine whether we can load + # the target if the @owner is currently a new record (and therefore + # without a key). + # + # Currently implemented by belongs_to (vanilla and polymorphic) and + # has_one/has_many :through associations which go through a belongs_to def foreign_key_present false end @@ -308,6 +312,15 @@ module ActiveRecord def invertible_for?(record) inverse_reflection_for(record) end + + # This should be implemented to return the values of the relevant key(s) on the owner, + # so that when state_state is different from the value stored on the last find_target, + # the target is stale. + # + # This is only relevant to certain associations, which is why it returns nil by default. + def stale_state + nil + end end end end -- cgit v1.2.3 From 4e194ed1e68c13901f486334a5a1e9f509b10722 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 2 Jan 2011 14:42:17 +0000 Subject: Rename AssociationProxy#foreign_key_present to foreign_key_present? --- activerecord/lib/active_record/associations/association_proxy.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 65fef81d64..e86f4c0283 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -270,7 +270,7 @@ module ActiveRecord def load_target return nil unless defined?(@loaded) - if !loaded? && (!@owner.new_record? || foreign_key_present) && @scope + if !loaded? && (!@owner.new_record? || foreign_key_present?) && @scope @target = find_target end @@ -287,7 +287,7 @@ module ActiveRecord # # Currently implemented by belongs_to (vanilla and polymorphic) and # has_one/has_many :through associations which go through a belongs_to - def foreign_key_present + def foreign_key_present? false end -- cgit v1.2.3 From 3103296a61709e808aa89c3d37cf22bcdbc5a675 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 2 Jan 2011 20:33:18 +0000 Subject: Let AssociationCollection#find use #scoped to do its finding. Note that I am removing test_polymorphic_has_many_going_through_join_model_with_disabled_include, since this specifies different behaviour for an association than for a regular scope. It seems reasonable to expect scopes and association proxies to behave in roughly the same way rather than having subtle differences. --- .../lib/active_record/associations/association_proxy.rb | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index e86f4c0283..ab42d0f215 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -182,20 +182,6 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Merges into +options+ the ones coming from the reflection. - def merge_options_from_reflection!(options) - options.reverse_merge!( - :group => @reflection.options[:group], - :having => @reflection.options[:having], - :limit => @reflection.options[:limit], - :offset => @reflection.options[:offset], - :joins => @reflection.options[:joins], - :include => @reflection.options[:include], - :select => @reflection.options[:select], - :readonly => @reflection.options[:readonly] - ) - end - # Forwards +with_scope+ to the reflection. def with_scope(*args, &block) target_klass.send :with_scope, *args, &block -- cgit v1.2.3 From 99a8d8430f9b819cd3e8cb3aab44cb04ea402532 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 3 Jan 2011 12:04:33 +0000 Subject: Create the association scope directly rather than going through with_scope --- .../associations/association_proxy.rb | 36 ++++++++++++---------- 1 file changed, 20 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index ab42d0f215..3a0cda49f8 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -169,7 +169,9 @@ module ActiveRecord end def scoped - with_scope(@scope) { target_klass.scoped } + target_scope. + apply_finder_options(@finder_options). + create_with(@creation_attributes) end protected @@ -182,30 +184,26 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Forwards +with_scope+ to the reflection. - def with_scope(*args, &block) - target_klass.send :with_scope, *args, &block - end - - # Construct the scope used for find/create queries on the target + # Construct the data used for the scope for this association + # + # Note that we don't actually build the scope here, we just construct the options and + # attributes. We must only build the scope when it's actually needed, because at that + # point the call may be surrounded by scope.scoping { ... } or with_scope { ... } etc, + # which affects the scope which actually gets built. def construct_scope if target_klass - @scope = { - :find => construct_find_scope, - :create => construct_create_scope - } - else - @scope = nil + @finder_options = finder_options + @creation_attributes = creation_attributes end end # Implemented by subclasses - def construct_find_scope + def finder_options raise NotImplementedError end # Implemented by (some) subclasses - def construct_create_scope + def creation_attributes {} end @@ -226,6 +224,12 @@ module ActiveRecord @reflection.klass end + # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the + # through association's scope) + def target_scope + target_klass.scoped + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) @@ -256,7 +260,7 @@ module ActiveRecord def load_target return nil unless defined?(@loaded) - if !loaded? && (!@owner.new_record? || foreign_key_present?) && @scope + if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass @target = find_target end -- cgit v1.2.3 From 9f1b0b32e27af668014c6fb21edbfc869f36dd2d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 5 Jan 2011 09:58:25 -0800 Subject: use attr_reader and alias methods to access instance variables --- .../lib/active_record/associations/association_proxy.rb | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 3a0cda49f8..8e64056a06 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -75,11 +75,6 @@ module ActiveRecord @reflection end - # Returns the \target of the proxy, same as +target+. - def proxy_target - @target - end - # Does the proxy or its \target respond to +symbol+? def respond_to?(*args) proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args)) @@ -143,9 +138,10 @@ module ActiveRecord end # Returns the target of this proxy, same as +proxy_target+. - def target - @target - end + attr_reader :target + + # Returns the \target of the proxy, same as +target+. + alias :proxy_target :target # Sets the target of this proxy to \target, and the \loaded flag to +true+. def target=(target) -- cgit v1.2.3 From 770e6893b9f2aaaebe3de10576931dc7194451bc Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 6 Jan 2011 18:04:32 +0000 Subject: Construct an actual ActiveRecord::Relation object for the association scope, rather than a hash which is passed to apply_finder_options. This allows more flexibility in how the scope is created, for example because scope.where(a, b) and scope.where(a).where(b) mean different things. --- .../associations/association_proxy.rb | 58 ++++++++++++++++------ 1 file changed, 42 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 8e64056a06..405a0307c1 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -165,9 +165,7 @@ module ActiveRecord end def scoped - target_scope. - apply_finder_options(@finder_options). - create_with(@creation_attributes) + target_scope & @association_scope end protected @@ -180,27 +178,32 @@ module ActiveRecord @reflection.klass.send(:sanitize_sql, sql, table_name) end - # Construct the data used for the scope for this association + # Construct the scope for this association. # - # Note that we don't actually build the scope here, we just construct the options and - # attributes. We must only build the scope when it's actually needed, because at that - # point the call may be surrounded by scope.scoping { ... } or with_scope { ... } etc, - # which affects the scope which actually gets built. + # Note that the association_scope is merged into the targed_scope only when the + # scoped method is called. This is because at that point the call may be surrounded + # by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which + # actually gets built. def construct_scope - if target_klass - @finder_options = finder_options - @creation_attributes = creation_attributes - end + @association_scope = association_scope if target_klass + end + + def association_scope + scope = target_klass.unscoped + scope = scope.create_with(creation_attributes) + scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include)) + scope = scope.where(construct_owner_conditions) + scope = scope.select(select_value) if select_value = self.select_value + scope end - # Implemented by subclasses - def finder_options - raise NotImplementedError + def select_value + @reflection.options[:select] end # Implemented by (some) subclasses def creation_attributes - {} + { } end def aliased_table @@ -226,6 +229,29 @@ module ActiveRecord target_klass.scoped end + # Returns a hash linking the owner to the association represented by the reflection + def construct_owner_attributes(reflection = @reflection) + attributes = {} + if reflection.macro == :belongs_to + attributes[reflection.association_primary_key] = @owner[reflection.foreign_key] + else + attributes[reflection.foreign_key] = @owner[reflection.active_record_primary_key] + + if reflection.options[:as] + attributes["#{reflection.options[:as]}_type"] = @owner.class.base_class.name + end + end + attributes + end + + # Builds an array of arel nodes from the owner attributes hash + def construct_owner_conditions(table = aliased_table, reflection = @reflection) + conditions = construct_owner_attributes(reflection).map do |attr, value| + table[attr].eq(value) + end + table.create_and(conditions) + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) -- cgit v1.2.3 From 45d0d18baef2de739dae89bb7bc79826392bbde5 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 6 Jan 2011 19:32:05 +0000 Subject: Not really worth having the HasAssociation module for just a single method --- .../lib/active_record/associations/association_proxy.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 405a0307c1..294e1cab50 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -9,11 +9,11 @@ module ActiveRecord # AssociationProxy # BelongsToAssociation # BelongsToPolymorphicAssociation - # AssociationCollection + HasAssociation + # AssociationCollection # HasAndBelongsToManyAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation - # HasOneAssociation + HasAssociation + # HasOneAssociation # HasOneThroughAssociation + ThroughAssociation # # Association proxies in Active Record are middlemen between the object that @@ -252,6 +252,13 @@ module ActiveRecord table.create_and(conditions) end + # Sets the owner attributes on the given record + def set_owner_attributes(record) + if @owner.persisted? + construct_owner_attributes.each { |key, value| record[key] = value } + end + end + private # Forwards any missing method call to the \target. def method_missing(method, *args) -- cgit v1.2.3 From 42b2e4f85bef64b7aa1382e96c79db1d4f318a56 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 9 Jan 2011 18:33:51 +0000 Subject: We can use the association_proxy method directly in HasOneThroughAssociation now --- .../associations/association_proxy.rb | 35 +++++++++++----------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 294e1cab50..5fbda0bd3d 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -259,23 +259,6 @@ module ActiveRecord end end - private - # Forwards any missing method call to the \target. - def method_missing(method, *args) - if load_target - unless @target.respond_to?(method) - message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" - raise NoMethodError, message - end - - if block_given? - @target.send(method, *args) { |*block_args| yield(*block_args) } - else - @target.send(method, *args) - end - end - end - # Loads the \target if needed and returns it. # # This method is abstract in the sense that it relies on +find_target+, @@ -299,6 +282,24 @@ module ActiveRecord reset end + private + + # Forwards any missing method call to the \target. + def method_missing(method, *args) + if load_target + unless @target.respond_to?(method) + message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" + raise NoMethodError, message + end + + if block_given? + @target.send(method, *args) { |*block_args| yield(*block_args) } + else + @target.send(method, *args) + end + end + end + # Should be true if there is a foreign key present on the @owner which # references the target. This is used to determine whether we can load # the target if the @owner is currently a new record (and therefore -- cgit v1.2.3 From 681ab53ba15f9fc95c8a91e50bb0138aa66967b2 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 9 Jan 2011 18:37:01 +0000 Subject: Get rid of set_association_target and association_loaded? as the parts of the code that need that can now just use association_proxy(:name).loaded?/target= --- activerecord/lib/active_record/associations/association_proxy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 5fbda0bd3d..844d30c3f5 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -213,7 +213,8 @@ module ActiveRecord # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) - record.send("set_#{inverse_reflection_for(record).name}_target", @owner) + inverse = record.send(:association_proxy, inverse_reflection_for(record).name) + inverse.target = @owner end end -- cgit v1.2.3 From f8700038afdaea80cad34a4fca005e1ef068b53e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 11 Jan 2011 17:57:02 -0800 Subject: adding a test for no method error --- .../lib/active_record/associations/association_proxy.rb | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 844d30c3f5..e4a449d4f4 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -286,19 +286,13 @@ module ActiveRecord private # Forwards any missing method call to the \target. - def method_missing(method, *args) + def method_missing(method, *args, &block) if load_target - unless @target.respond_to?(method) - message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" - raise NoMethodError, message - end - - if block_given? - @target.send(method, *args) { |*block_args| yield(*block_args) } - else - @target.send(method, *args) - end + return super unless @target.respond_to?(method) + @target.send(method, *args, &block) end + rescue NoMethodError => e + raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}") end # Should be true if there is a foreign key present on the @owner which -- cgit v1.2.3 From 8bee98fe3a3917f86d53f68b7cc11c4aafe5f011 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Jan 2011 12:09:40 -0800 Subject: just use respond_to? and super rather than aliasing around methods --- activerecord/lib/active_record/associations/association_proxy.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index e4a449d4f4..ba784fdb9d 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -50,10 +50,9 @@ module ActiveRecord # is computed directly through SQL and does not trigger by itself the # instantiation of the actual post records. class AssociationProxy #:nodoc: - alias_method :proxy_respond_to?, :respond_to? alias_method :proxy_extend, :extend delegate :to_param, :to => :proxy_target - instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to_missing|proxy_/ } + instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } def initialize(owner, reflection) @owner, @reflection = owner, reflection @@ -77,7 +76,7 @@ module ActiveRecord # Does the proxy or its \target respond to +symbol+? def respond_to?(*args) - proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args)) + super || (load_target && @target.respond_to?(*args)) end # Forwards === explicitly to the \target because the instance method @@ -156,7 +155,7 @@ module ActiveRecord end def send(method, *args) - if proxy_respond_to?(method) + if respond_to?(method) super else load_target -- cgit v1.2.3 From 49696e0a62f39648d82660c689d15e56bb5f5207 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Jan 2011 12:12:42 -0800 Subject: @loaded is defined in initialize, so we should not need this --- activerecord/lib/active_record/associations/association_proxy.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index ba784fdb9d..c19ded242e 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -270,8 +270,6 @@ module ActiveRecord # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target - return nil unless defined?(@loaded) - if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass @target = find_target end -- cgit v1.2.3 From bc993c690b656256e0f1dc5336a2f69c9d8dbe5d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Jan 2011 13:22:42 -0800 Subject: default return value is nil --- activerecord/lib/active_record/associations/association_proxy.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index c19ded242e..ee0631edb6 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -331,7 +331,6 @@ module ActiveRecord # # This is only relevant to certain associations, which is why it returns nil by default. def stale_state - nil end end end -- cgit v1.2.3 From e9980f17fdca21655a9804d69632d83451067f58 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Jan 2011 14:26:57 -0800 Subject: just call methods on return value of `load_target` --- .../lib/active_record/associations/association_proxy.rb | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index ee0631edb6..a51adce958 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -150,17 +150,12 @@ module ActiveRecord # Forwards the call to the target. Loads the \target if needed. def inspect - load_target - @target.inspect + load_target.inspect end def send(method, *args) - if respond_to?(method) - super - else - load_target - @target.send(method, *args) - end + return super if respond_to?(method) + load_target.send(method, *args) end def scoped -- cgit v1.2.3 From 8f9944d5bcd684fedbd23e4fd77a2dabfaff6fad Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 12 Jan 2011 14:29:17 -0800 Subject: just use return value of load_target --- activerecord/lib/active_record/associations/association_proxy.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index a51adce958..f2de788522 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -82,8 +82,7 @@ module ActiveRecord # Forwards === explicitly to the \target because the instance method # removal above doesn't catch it. Loads the \target if needed. def ===(other) - load_target - other === @target + other === load_target end # Returns the name of the table of the related class: -- cgit v1.2.3 From ef4ffed660015772f67826e8aaa319febe84f271 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Jan 2011 14:30:47 -0800 Subject: reduce some lasigns --- activerecord/lib/active_record/associations/association_proxy.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index f2de788522..b752f1fdc0 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -185,9 +185,8 @@ module ActiveRecord scope = target_klass.unscoped scope = scope.create_with(creation_attributes) scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include)) - scope = scope.where(construct_owner_conditions) scope = scope.select(select_value) if select_value = self.select_value - scope + scope.where(construct_owner_conditions) end def select_value -- cgit v1.2.3 From dc11a77ab7730ec213b5042e1261a7be8c211396 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 14 Jan 2011 14:40:11 -0800 Subject: write the delegate method directly to avoid `delegate` callstack overhead --- activerecord/lib/active_record/associations/association_proxy.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index b752f1fdc0..7f6e335858 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -51,7 +51,7 @@ module ActiveRecord # instantiation of the actual post records. class AssociationProxy #:nodoc: alias_method :proxy_extend, :extend - delegate :to_param, :to => :proxy_target + instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } def initialize(owner, reflection) @@ -63,6 +63,10 @@ module ActiveRecord construct_scope end + def to_param + proxy_target.to_param + end + # Returns the owner of the proxy. def proxy_owner @owner -- cgit v1.2.3 From ef79b917848f07b61e3243027f5e2ce4bc006d78 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 16 Jan 2011 19:34:08 +0000 Subject: Abstract common code from BelongsToAssociation and HasOneAssociation into SingularAssociation --- activerecord/lib/active_record/associations/association_proxy.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 7f6e335858..addc64cb42 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -7,14 +7,15 @@ module ActiveRecord # This is the root class of all association proxies ('+ Foo' signifies an included module Foo): # # AssociationProxy - # BelongsToAssociation - # BelongsToPolymorphicAssociation + # SingularAssociaton + # HasOneAssociation + # HasOneThroughAssociation + ThroughAssociation + # BelongsToAssociation + # BelongsToPolymorphicAssociation # AssociationCollection # HasAndBelongsToManyAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation - # HasOneAssociation - # HasOneThroughAssociation + ThroughAssociation # # Association proxies in Active Record are middlemen between the object that # holds the association, known as the @owner, and the actual associated -- cgit v1.2.3 From 15601c52e7c7094a6b7b54ef8acfc8299a4d6724 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 24 Jan 2011 19:28:53 +0000 Subject: =?UTF-8?q?Let's=20be=20less=20blas=C3=A9=20about=20method=20visib?= =?UTF-8?q?ility=20on=20association=20proxies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../associations/association_proxy.rb | 105 +++++++++++---------- 1 file changed, 54 insertions(+), 51 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index addc64cb42..e7c9bbd192 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -84,6 +84,16 @@ module ActiveRecord super || (load_target && @target.respond_to?(*args)) end + # Forwards any missing method call to the \target. + def method_missing(method, *args, &block) + if load_target + return super unless @target.respond_to?(method) + @target.send(method, *args, &block) + end + rescue NoMethodError => e + raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}") + end + # Forwards === explicitly to the \target because the instance method # removal above doesn't catch it. Loads the \target if needed. def ===(other) @@ -167,14 +177,6 @@ module ActiveRecord end protected - def interpolate_sql(sql, record = nil) - @owner.send(:interpolate_sql, sql, record) - end - - # Forwards the call to the reflection class. - def sanitize_sql(sql, table_name = @reflection.klass.table_name) - @reflection.klass.send(:sanitize_sql, sql, table_name) - end # Construct the scope for this association. # @@ -190,19 +192,12 @@ module ActiveRecord scope = target_klass.unscoped scope = scope.create_with(creation_attributes) scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include)) - scope = scope.select(select_value) if select_value = self.select_value + if select = select_value + scope = scope.select(select) + end scope.where(construct_owner_conditions) end - def select_value - @reflection.options[:select] - end - - # Implemented by (some) subclasses - def creation_attributes - { } - end - def aliased_table target_klass.arel_table end @@ -227,6 +222,47 @@ module ActiveRecord target_klass.scoped end + # Loads the \target if needed and returns it. + # + # This method is abstract in the sense that it relies on +find_target+, + # which is expected to be provided by descendants. + # + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. + # + # ActiveRecord::RecordNotFound is rescued within the method, and it is + # not reraised. The proxy is \reset and +nil+ is the return value. + def load_target + if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass + @target = find_target + end + + loaded + @target + rescue ActiveRecord::RecordNotFound + reset + end + + private + + def interpolate_sql(sql, record = nil) + @owner.send(:interpolate_sql, sql, record) + end + + # Forwards the call to the reflection class. + def sanitize_sql(sql, table_name = @reflection.klass.table_name) + @reflection.klass.send(:sanitize_sql, sql, table_name) + end + + def select_value + @reflection.options[:select] + end + + # Implemented by (some) subclasses + def creation_attributes + { } + end + # Returns a hash linking the owner to the association represented by the reflection def construct_owner_attributes(reflection = @reflection) attributes = {} @@ -257,39 +293,6 @@ module ActiveRecord end end - # Loads the \target if needed and returns it. - # - # This method is abstract in the sense that it relies on +find_target+, - # which is expected to be provided by descendants. - # - # If the \target is already \loaded it is just returned. Thus, you can call - # +load_target+ unconditionally to get the \target. - # - # ActiveRecord::RecordNotFound is rescued within the method, and it is - # not reraised. The proxy is \reset and +nil+ is the return value. - def load_target - if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass - @target = find_target - end - - loaded - @target - rescue ActiveRecord::RecordNotFound - reset - end - - private - - # Forwards any missing method call to the \target. - def method_missing(method, *args, &block) - if load_target - return super unless @target.respond_to?(method) - @target.send(method, *args, &block) - end - rescue NoMethodError => e - raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@target}") - end - # Should be true if there is a foreign key present on the @owner which # references the target. This is used to determine whether we can load # the target if the @owner is currently a new record (and therefore -- cgit v1.2.3 From d392c67d2c614644d678627e6cd0124878982fc7 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 24 Jan 2011 20:04:00 +0000 Subject: Remove unused methods conditions, sql_conditions and sanitize_sql --- .../lib/active_record/associations/association_proxy.rb | 12 ------------ 1 file changed, 12 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index e7c9bbd192..59b0c54f2f 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -108,13 +108,6 @@ module ActiveRecord @reflection.klass.table_name end - # Returns the SQL string that corresponds to the :conditions - # option of the macro, if given, or +nil+ otherwise. - def conditions - @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions - end - alias :sql_conditions :conditions - # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @@ -249,11 +242,6 @@ module ActiveRecord @owner.send(:interpolate_sql, sql, record) end - # Forwards the call to the reflection class. - def sanitize_sql(sql, table_name = @reflection.klass.table_name) - @reflection.klass.send(:sanitize_sql, sql, table_name) - end - def select_value @reflection.options[:select] end -- cgit v1.2.3 From de05e2fb15ee4fd521aae202eb4517ae05114c28 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 24 Jan 2011 20:47:06 +0000 Subject: Abstract load_target conditional logic --- .../lib/active_record/associations/association_proxy.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 59b0c54f2f..ead2c5ede2 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -226,18 +226,19 @@ module ActiveRecord # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target - if !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass - @target = find_target - end - + @target = find_target if find_target? loaded - @target + target rescue ActiveRecord::RecordNotFound reset end private + def find_target? + !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass + end + def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) end -- cgit v1.2.3 From aa86420be24d7df9c07379bcf6f33904d0d41adc Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 24 Jan 2011 20:57:11 +0000 Subject: Rename AssociationProxy#loaded to loaded! as it mutates the association --- activerecord/lib/active_record/associations/association_proxy.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index ead2c5ede2..8e16246e80 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -128,7 +128,7 @@ module ActiveRecord end # Asserts the \target has been loaded setting the \loaded flag to +true+. - def loaded + def loaded! @loaded = true @stale_state = stale_state end @@ -152,7 +152,7 @@ module ActiveRecord # Sets the target of this proxy to \target, and the \loaded flag to +true+. def target=(target) @target = target - loaded + loaded! end # Forwards the call to the target. Loads the \target if needed. @@ -227,7 +227,7 @@ module ActiveRecord # not reraised. The proxy is \reset and +nil+ is the return value. def load_target @target = find_target if find_target? - loaded + loaded! target rescue ActiveRecord::RecordNotFound reset -- cgit v1.2.3 From 6bd9fac1e301d57765073e1f7a17e46972428205 Mon Sep 17 00:00:00 2001 From: Glenn Vanderburg Date: Fri, 28 Jan 2011 18:16:44 -0600 Subject: Propagate association extensions to scopes called on the association. Signed-off-by: Santiago Pastorino --- activerecord/lib/active_record/associations/association_proxy.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 8e16246e80..84f868ec43 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -188,6 +188,9 @@ module ActiveRecord if select = select_value scope = scope.select(select) end + if Relation === scope + scope = scope.extending(*Array.wrap(@reflection.options[:extend])) + end scope.where(construct_owner_conditions) end -- cgit v1.2.3 From 451ad38bb2eafbaff48697f6d38f27cbc9cc6e9e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 1 Feb 2011 17:20:24 -0200 Subject: scope is always a Relation --- activerecord/lib/active_record/associations/association_proxy.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 84f868ec43..07fff7f7d7 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -188,9 +188,7 @@ module ActiveRecord if select = select_value scope = scope.select(select) end - if Relation === scope - scope = scope.extending(*Array.wrap(@reflection.options[:extend])) - end + scope = scope.extending(*Array.wrap(@reflection.options[:extend])) scope.where(construct_owner_conditions) end -- cgit v1.2.3 From fbd917f50a6046d02dd6a64ccfb1aed0cbce68d8 Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Thu, 10 Feb 2011 14:03:25 -0500 Subject: Remove Relation#& alias for Relation#merge --- activerecord/lib/active_record/associations/association_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 07fff7f7d7..2832f49c23 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -166,7 +166,7 @@ module ActiveRecord end def scoped - target_scope & @association_scope + target_scope.merge(@association_scope) end protected -- cgit v1.2.3 From a7e19b30ca71f62af516675023659be061b2b70a Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 11 Feb 2011 22:22:19 +0000 Subject: Add interpolation of association conditions back in, in the form of proc { ... } rather than instance_eval-ing strings --- .../lib/active_record/associations/association_proxy.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/associations/association_proxy.rb') diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index 2832f49c23..fc03a4b619 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -184,7 +184,8 @@ module ActiveRecord def association_scope scope = target_klass.unscoped scope = scope.create_with(creation_attributes) - scope = scope.apply_finder_options(@reflection.options.slice(:conditions, :readonly, :include)) + scope = scope.apply_finder_options(@reflection.options.slice(:readonly, :include)) + scope = scope.where(interpolate(@reflection.options[:conditions])) if select = select_value scope = scope.select(select) end @@ -240,8 +241,12 @@ module ActiveRecord !loaded? && (!@owner.new_record? || foreign_key_present?) && target_klass end - def interpolate_sql(sql, record = nil) - @owner.send(:interpolate_sql, sql, record) + def interpolate(sql, record = nil) + if sql.respond_to?(:to_proc) + @owner.send(:instance_exec, record, &sql) + else + sql + end end def select_value -- cgit v1.2.3