diff options
Diffstat (limited to 'activerecord/lib/active_record/associations/association.rb')
-rw-r--r-- | activerecord/lib/active_record/associations/association.rb | 65 |
1 files changed, 38 insertions, 27 deletions
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index 3f0e4ca999..f1c36cd047 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -13,11 +13,11 @@ module ActiveRecord # BelongsToAssociation # BelongsToPolymorphicAssociation # CollectionAssociation - # HasAndBelongsToManyAssociation # HasManyAssociation # HasManyThroughAssociation + ThroughAssociation class Association #:nodoc: attr_reader :owner, :target, :reflection + attr_accessor :inversed delegate :options, :to => :reflection @@ -30,7 +30,7 @@ module ActiveRecord reset_scope end - # Returns the name of the table of the related class: + # Returns the name of the table of the associated class: # # post.comments.aliased_table_name # => "comments" # @@ -43,6 +43,7 @@ module ActiveRecord @loaded = false @target = nil @stale_state = nil + @inversed = false end # Reloads the \target and returns +self+ on success. @@ -60,18 +61,19 @@ module ActiveRecord # Asserts the \target has been loaded setting the \loaded flag to +true+. def loaded! - @loaded = true + @loaded = true @stale_state = stale_state + @inversed = false 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 the - # state_state method if relevant. + # stale_state method if relevant. # # Note that if the target has not been loaded, it is not considered stale. def stale_target? - loaded? && @stale_state != stale_state + !inversed && loaded? && @stale_state != stale_state end # Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+. @@ -84,20 +86,15 @@ module ActiveRecord target_scope.merge(association_scope) end - def scoped - ActiveSupport::Deprecation.warn "#scoped is deprecated. use #scope instead." - scope - end - # The scope for this association. # # Note that the association_scope is merged into the target_scope only when the - # scoped method is called. This is because at that point the call may be surrounded + # scope 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 association_scope if klass - @association_scope ||= AssociationScope.new(self).scope + @association_scope ||= AssociationScope.scope(self, klass.connection) end end @@ -107,13 +104,15 @@ module ActiveRecord # Set the inverse association, if possible def set_inverse_instance(record) - if record && invertible_for?(record) + if invertible_for?(record) inverse = record.association(inverse_reflection_for(record).name) inverse.target = owner + inverse.inversed = true end + record end - # This class of the target. belongs_to polymorphic overrides this to look at the + # Returns the class of the target. belongs_to polymorphic overrides this to look at the # polymorphic_type field on the owner. def klass reflection.klass @@ -122,7 +121,7 @@ module ActiveRecord # Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the # through association's scope) def target_scope - klass.all + AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all) end # Loads the \target if needed and returns it. @@ -146,7 +145,7 @@ module ActiveRecord def interpolate(sql, record = nil) if sql.respond_to?(:to_proc) - owner.send(:instance_exec, record, &sql) + owner.instance_exec(record, &sql) else sql end @@ -161,7 +160,14 @@ module ActiveRecord def marshal_load(data) reflection_name, ivars = data ivars.each { |name, val| instance_variable_set(name, val) } - @reflection = @owner.class.reflect_on_association(reflection_name) + @reflection = @owner.class._reflect_on_association(reflection_name) + end + + def initialize_attributes(record) #:nodoc: + skip_assign = [reflection.foreign_key, reflection.type].compact + attributes = create_scope.except(*(record.changed - skip_assign)) + record.assign_attributes(attributes) + set_inverse_instance(record) end private @@ -173,7 +179,7 @@ module ActiveRecord def creation_attributes attributes = {} - if (reflection.macro == :has_one || reflection.macro == :has_many) && !options[:through] + if (reflection.has_one? || reflection.collection?) && !options[:through] attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key] if reflection.options[:as] @@ -189,13 +195,14 @@ module ActiveRecord creation_attributes.each { |key, value| record[key] = value } end - # Should be true if there is a foreign key present on the owner which + # Returns 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). + # without a key). If the owner is a new record then foreign_key must + # be present in order to load target. # # Currently implemented by belongs_to (vanilla and polymorphic) and - # has_one/has_many :through associations which go through a belongs_to + # has_one/has_many :through associations which go through a belongs_to. def foreign_key_present? false end @@ -203,7 +210,7 @@ module ActiveRecord # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of # the kind of the class of the associated objects. Meant to be used as # a sanity check when you are about to assign an associated record. - def raise_on_type_mismatch(record) + def raise_on_type_mismatch!(record) unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize) message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message @@ -217,9 +224,15 @@ module ActiveRecord reflection.inverse_of end - # Is this association invertible? Can be redefined by subclasses. + # Returns true if inverse association on the given record needs to be set. + # This method is redefined by subclasses. def invertible_for?(record) - inverse_reflection_for(record) + foreign_key_for?(record) && inverse_reflection_for(record) + end + + # Returns true if record contains the foreign_key + def foreign_key_for?(record) + record.has_attribute?(reflection.foreign_key) end # This should be implemented to return the values of the relevant key(s) on the owner, @@ -232,9 +245,7 @@ module ActiveRecord def build_record(attributes) reflection.build_association(attributes) do |record| - skip_assign = [reflection.foreign_key, reflection.type].compact - attributes = create_scope.except(*(record.changed - skip_assign)) - record.assign_attributes(attributes) + initialize_attributes(record) end end end |