class ActiveRecord::Associations::Association
:nodoc:
the reflection
object represents a :has_many
macro.owner
, the collection of its posts as target
, and
The association of blog.posts
has the object blog
as its
blog = Blog.first
end
has_many :posts
class Blog < ActiveRecord::Base
For example, givenreflection
, which is an instance of ActiveRecord::Reflection::AssociationReflection
.
result set, known as the target
. Association metadata is available in
holds the association, known as the owner
, and the associated
Associations in Active Record are middlemen between the object that
HasManyThroughAssociation + ThroughAssociation
HasManyAssociation + ForeignAssociation
CollectionAssociation
BelongsToPolymorphicAssociation
BelongsToAssociation
HasOneThroughAssociation + ThroughAssociation
HasOneAssociation + ForeignAssociation
SingularAssociation
Association
This is the root class of all associations (‘+ Foo’ signifies an included module Foo):
= Active Record Associations
def association_scope
by scope.scoping { ... } or unscoped { ... } etc, which affects the scope which
scope method is called. This is because at that point the call may be surrounded
Note that the association_scope is merged into the target_scope only when the
The scope for this association.
def association_scope if klass @association_scope ||= if disable_joins DisableJoinsAssociationScope.scope(self) else AssociationScope.scope(self) end end end
def build_record(attributes)
def build_record(attributes) reflection.build_association(attributes) do |record| initialize_attributes(record, attributes) yield(record) if block_given? end end
def create(attributes = nil, &block)
def create(attributes = nil, &block) _create_record(attributes, &block) end
def create!(attributes = nil, &block)
def create!(attributes = nil, &block) _create_record(attributes, true, &block) end
def enqueue_destroy_association(options)
def enqueue_destroy_association(options) job_class = owner.class.destroy_association_async_job if job_class owner._after_commit_jobs.push([job_class, options]) end end
def ensure_klass_exists!
Reader and writer methods call this so that consistent errors are presented
def ensure_klass_exists! klass end
def extensions
def extensions extensions = klass.default_extensions | reflection.extensions if reflection.scope extensions |= reflection.scope_for(klass.unscoped, owner).extensions end extensions end
def find_target
def find_target if violates_strict_loading? && owner.validation_context.nil? Base.strict_loading_violation!(owner: owner.class, reflection: reflection) end scope = self.scope return scope.to_a if skip_statement_cache?(scope) sc = reflection.association_scope_cache(klass, owner) do |params| as = AssociationScope.create { params.bind } target_scope.merge!(as.scope(self)) end binds = AssociationScope.get_bind_values(owner, reflection.chain) sc.execute(binds, klass.connection) do |record| set_inverse_instance(record) if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many record.strict_loading! else record.strict_loading!(false, mode: owner.strict_loading_mode) end end end
def find_target?
def find_target? !loaded? && (!owner.new_record? || foreign_key_present?) && klass end
def foreign_key_for?(record)
def foreign_key_for?(record) record._has_attribute?(reflection.foreign_key) end
def foreign_key_present?
Currently implemented by belongs_to (vanilla and polymorphic) and
be present in order to load target.
without a key). If the owner is a new record then foreign_key must
the target if the owner is currently a new record (and therefore
references the target. This is used to determine whether we can load
Returns true if there is a foreign key present on the owner which
def foreign_key_present? false end
def initialize(owner, reflection)
def initialize(owner, reflection) reflection.check_validity! @owner, @reflection = owner, reflection @disable_joins = @reflection.options[:disable_joins] || false reset reset_scope end
def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc:
def initialize_attributes(record, except_from_scope_attributes = nil) # :nodoc: except_from_scope_attributes ||= {} skip_assign = [reflection.foreign_key, reflection.type].compact assigned_keys = record.changed_attribute_names_to_save assigned_keys += except_from_scope_attributes.keys.map(&:to_s) attributes = scope_for_create.except!(*(assigned_keys - skip_assign)) record.send(:_assign_attributes, attributes) if attributes.any? set_inverse_instance(record) end
def inversable?(record)
def inversable?(record) record && ((!record.persisted? || !owner.persisted?) || matches_foreign_key?(record)) end
def inverse_association_for(record)
def inverse_association_for(record) if invertible_for?(record) record.association(inverse_reflection_for(record).name) end end
def inverse_reflection_for(record)
The record parameter is necessary to support polymorphic inverses as we must check for
Can be redefined by subclasses, notably polymorphic belongs_to
def inverse_reflection_for(record) reflection.inverse_of end
def inversed_from(record)
def inversed_from(record) self.target = record end
def inversed_from_queries(record)
def inversed_from_queries(record) if inversable?(record) self.target = record end end
def invertible_for?(record)
Returns true if inverse association on the given record needs to be set.
def invertible_for?(record) foreign_key_for?(record) && inverse_reflection_for(record) end
def klass
Returns the class of the target. belongs_to polymorphic overrides this to look at the
def klass reflection.klass end
def load_target
ActiveRecord::RecordNotFound is rescued within the method, and it is
+load_target+ unconditionally to get the \target.
If the \target is already \loaded it is just returned. Thus, you can call
which is expected to be provided by descendants.
This method is abstract in the sense that it relies on +find_target+,
Loads the \target if needed and returns it.
def load_target @target = find_target if (@stale_state && stale_target?) || find_target? loaded! unless loaded? target rescue ActiveRecord::RecordNotFound reset end
def loaded!
def loaded! @loaded = true @stale_state = stale_state end
def loaded?
def loaded? @loaded end
def marshal_dump
def marshal_dump ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] } [@reflection.name, ivars] end
def marshal_load(data)
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) end
def matches_foreign_key?(record)
def matches_foreign_key?(record) if foreign_key_for?(record) record.read_attribute(reflection.foreign_key) == owner.id || (foreign_key_for?(owner) && owner.read_attribute(reflection.foreign_key) == record.id) else owner.read_attribute(reflection.foreign_key) == record.id end end
def raise_on_type_mismatch!(record)
the kind of the class of the associated objects. Meant to be used as
Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
def raise_on_type_mismatch!(record) unless record.is_a?(reflection.klass) fresh_class = reflection.class_name.safe_constantize unless fresh_class && record.is_a?(fresh_class) message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, "\ "got #{record.inspect} which is an instance of #{record.class}(##{record.class.object_id})" raise ActiveRecord::AssociationTypeMismatch, message end end end
def reload(force = false)
Reloads the \target and returns +self+ on success.
def reload(force = false) klass.connection.clear_query_cache if force && klass reset reset_scope load_target self unless target.nil? end
def remove_inverse_instance(record)
def remove_inverse_instance(record) if inverse = inverse_association_for(record) inverse.inversed_from(nil) end end
def reset
def reset @loaded = false @target = nil @stale_state = nil end
def reset_negative_cache # :nodoc:
def reset_negative_cache # :nodoc: reset if loaded? && target.nil? end
def reset_scope
def reset_scope @association_scope = nil end
def scope
def scope if disable_joins DisableJoinsAssociationScope.create.scope(self) elsif (scope = klass.current_scope) && scope.try(:proxy_association) == self scope.spawn elsif scope = klass.global_current_scope target_scope.merge!(association_scope).merge!(scope) else target_scope.merge!(association_scope) end end
def scope_for_create
def scope_for_create scope.scope_for_create end
def set_inverse_instance(record)
def set_inverse_instance(record) if inverse = inverse_association_for(record) inverse.inversed_from(owner) end record end
def set_inverse_instance_from_queries(record)
def set_inverse_instance_from_queries(record) if inverse = inverse_association_for(record) inverse.inversed_from_queries(owner) end record end
def skip_statement_cache?(scope)
def skip_statement_cache?(scope) reflection.has_scope? || scope.eager_loading? || klass.scope_attributes? || reflection.source_reflection.active_record.default_scopes.any? end
def stale_state
the target is stale.
so that when stale_state is different from the value stored on the last find_target,
This should be implemented to return the values of the relevant key(s) on the owner,
def stale_state end
def stale_target?
stale_state method if relevant.
on the owner will reload the target. It's up to subclasses to implement the
relevant foreign_key(s) refers to. If stale, the association accessor method
The target is stale if the target no longer points to the record(s) that the
def stale_target? loaded? && @stale_state != stale_state end
def target=(target)
def target=(target) @target = target loaded! end
def target_scope
Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
def target_scope AssociationRelation.create(klass, self).merge!(klass.scope_for_association) end
def violates_strict_loading?
def violates_strict_loading? return reflection.strict_loading? if reflection.options.key?(:strict_loading) owner.strict_loading? && !owner.strict_loading_n_plus_one_only? end