module ActiveRecord::Associations::ThroughAssociation
def build_record(attributes)
def build_record(attributes) if source_reflection.collection? inverse = source_reflection.inverse_of target = through_association.target if inverse && target && !target.is_a?(Array) Array(target.id).zip(Array(inverse.foreign_key)).map do |primary_key_value, foreign_key_column| attributes[foreign_key_column] = primary_key_value end end end super end
def construct_join_attributes(*records)
situation it is more natural for the user to just create or modify their join records
some other type, because this opens up a whole can of worms, and in basically any
We do not support creating/deleting records on the association where the source has
be represented as has_and_belongs_to_many associations.
typically has a belongs_to on both side. In other words, associations which could also
This is the "has_many :tags, through: :taggings" situation, where the join model
We only support indirectly modifying through associations which have a belongs_to source.
methods which create and delete records on the association.
Construct attributes for :through pointing to owner and associate. This is used by the
def construct_join_attributes(*records) ensure_mutable association_primary_key = source_reflection.association_primary_key(reflection.klass) if Array(association_primary_key) == reflection.klass.composite_query_constraints_list && !options[:source_type] join_attributes = { source_reflection.name => records } else assoc_pk_values = records.map { |record| record._read_attribute(association_primary_key) } join_attributes = { source_reflection.foreign_key => assoc_pk_values } end if options[:source_type] join_attributes[source_reflection.foreign_type] = [ options[:source_type] ] end if records.count == 1 join_attributes.transform_values!(&:first) else join_attributes end end
def ensure_mutable
def ensure_mutable unless source_reflection.belongs_to? if reflection.has_one? raise HasOneThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) else raise HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(owner, reflection) end end end
def ensure_not_nested
def ensure_not_nested if reflection.nested? if reflection.has_one? raise HasOneThroughNestedAssociationsAreReadonly.new(owner, reflection) else raise HasManyThroughNestedAssociationsAreReadonly.new(owner, reflection) end end end
def foreign_key_present?
def foreign_key_present? through_reflection.belongs_to? && Array(through_reflection.foreign_key).all? do |foreign_key_column| !owner[foreign_key_column].nil? end end
def stale_state
Note: this does not capture all cases, for example it would be impractical
def stale_state if through_reflection.belongs_to? Array(through_reflection.foreign_key).filter_map do |foreign_key_column| owner[foreign_key_column] end.presence end end
def target_scope
1. To get the default_scope conditions for any of the other reflections in the chain
We merge in these scopes for two reasons:
def target_scope scope = super reflection.chain.drop(1).each do |reflection| relation = reflection.klass.scope_for_association scope.merge!( relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins) ) end scope end
def through_association
def through_association @through_association ||= owner.association(through_reflection.name) end
def through_reflection
def through_reflection @through_reflection ||= begin refl = reflection.through_reflection while refl.through_reflection? refl = refl.through_reflection end refl end end
def transaction(&block)
def transaction(&block) through_reflection.klass.transaction(&block) end