123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Associations::BelongsToAssociation

Do not use. This class is for internal use only.
Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: ActiveRecord::Associations::SingularAssociation
Defined in: activerecord/lib/active_record/associations/belongs_to_association.rb

Overview

Active Record Belongs To Association

Class Method Summary

Instance Attribute Summary

Association - Inherited

#collection?

Whether the association represents a single record or a collection of records.

#disable_joins,
#loaded?

Has the \target been already \loaded?

#options, #owner, #reflection,
#stale_target?

The target is stale if the target no longer points to the record(s) that the relevant foreign_key(s) refers to.

#target,
#target=

Sets the target of this association to \target, and the \loaded flag to true.

#violates_strict_loading?, #find_target?,
#foreign_key_present?

Returns true if there is a foreign key present on the owner which references the target.

Instance Method Summary

SingularAssociation - Inherited

#build,
#force_reload_reader

Implements the reload reader method, e.g.

#reader

Implements the reader method, e.g.

#reset

Resets the \loaded flag to false and sets the \target to nil.

#writer

Implements the writer method, e.g.

#_create_record, #find_target, #replace, #scope_for_create, #set_new_record

Association - Inherited

#async_load_target, #create, #create!, #extensions, #initialize_attributes, #inversed_from, #inversed_from_queries,
#klass

Returns the class of the target.

#load_target

Loads the \target if needed and returns it.

#loaded!

Asserts the \target has been loaded setting the \loaded flag to true.

#marshal_dump

We can't dump @reflection and @through_reflection since it contains the scope proc.

#marshal_load,
#reload

Reloads the \target and returns self on success.

#remove_inverse_instance

Remove the inverse association, if possible.

#reset

Resets the \loaded flag to false and sets the \target to nil.

#reset_negative_cache, #reset_scope, #scope,
#set_inverse_instance

Set the inverse association, if possible.

#set_inverse_instance_from_queries, #set_strict_loading, #active_record_primary_key_values, #association_primary_key_values,
#association_scope

The scope for this association.

#build_record, #enqueue_destroy_association,
#ensure_klass_exists!

Reader and writer methods call this so that consistent errors are presented when the association target class does not exist.

#find_target,
#foreign_key_for?

Returns true if record contains the foreign_key.

#foreign_key_values, #inversable?, #inverse_association_for,
#inverse_reflection_for

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.

#invertible_for?

Returns true if inverse association on the given record needs to be set.

#matches_foreign_key?, #owner_foreign_key_matches_record?,
#raise_on_type_mismatch!

Raises ::ActiveRecord::AssociationTypeMismatch unless record is of the kind of the class of the associated objects.

#record_foreign_key_matches_owner?, #scope_for_create,
#skip_statement_cache?

Returns true if statement cache should be skipped on the association reader.

#skip_strict_loading,
#stale_state

This should be implemented to return the values of the relevant key(s) on the owner, so that when stale_state is different from the value stored on the last find_target, the target is stale.

#target_scope

Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e.

Constructor Details

.new(owner, reflection) ⇒ BelongsToAssociation

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 9

def initialize(owner, reflection)
  super
  aliases = owner.class.attribute_aliases
  fk = reflection.foreign_key
  resolved_fk = fk.is_a?(Array) ? fk.map { |k| aliases[k] || k } : (aliases[fk] || fk)
  @foreign_key = ActiveRecord::Key.for(resolved_fk)
  if reflection.polymorphic?
    ft = reflection.foreign_type
    @foreign_type = aliases[ft] || ft
  end
end

Instance Attribute Details

#find_target?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 135

def find_target?
  !loaded? && foreign_key_present? && klass
end

#foreign_key (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 7

attr_reader :foreign_key, :foreign_type

#foreign_key_present?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 165

def foreign_key_present?
  foreign_key.all? { |fk| owner.read_attribute(fk) }
end

#foreign_type (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 7

attr_reader :foreign_key, :foreign_type

#require_counter_update?Boolean (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 139

def require_counter_update?
  reflection.counter_cache_column && owner.persisted?
end

#saved_change_to_target?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 100

def saved_change_to_target?
  foreign_key.any? { |fk| owner.saved_change_to_attribute?(fk) }
end

#target_changed?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 92

def target_changed?
  foreign_key.any? { |fk| owner.attribute_changed?(fk) } || (!foreign_key_present? && target&.new_record?)
end

#target_previously_changed?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 96

def target_previously_changed?
  foreign_key.any? { |fk| owner.attribute_previously_changed?(fk) }
end

#updated?Boolean (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 64

def updated?
  @updated
end

Instance Method Details

#decrement_counters

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 68

def decrement_counters
  update_counters(-1)
end

#decrement_counters_before_last_save

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 76

def decrement_counters_before_last_save
  if reflection.polymorphic?
    model_type_was = owner.attribute_before_last_save(foreign_type)
    model_was = owner.class.polymorphic_class_for(model_type_was) if model_type_was
  else
    model_was = klass
  end

  values = foreign_key.map { |fk| owner.attribute_before_last_save(fk) }
  foreign_key_was = foreign_key.composite? ? (values if values.all?) : values.first

  if foreign_key_was && model_was < ActiveRecord::Base
    update_counters_via_scope(model_was, foreign_key_was, -1)
  end
end

#default(&block)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 55

def default(&block)
  writer(owner.instance_exec(&block)) if reader.nil?
end

#handle_dependency

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 21

def handle_dependency
  return unless load_target

  case options[:dependent]
  when :destroy
    raise ActiveRecord::Rollback unless target.destroy
  when :destroy_async
    primary_key_column = reflection.active_record_primary_key
    ids = foreign_key.map { |col| owner.public_send(col) }

    association_class = if reflection.polymorphic?
      owner.public_send(foreign_type)
    else
      reflection.klass
    end

    enqueue_destroy_association(
      owner_model_name: owner.class.to_s,
      owner_id: owner.id,
      association_class: association_class.to_s,
      association_ids: foreign_key.composite? ? [ids] : ids,
      association_primary_key_column: primary_key_column,
      ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
    )
  else
    target.public_send(options[:dependent])
  end
end

#increment_counters

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 72

def increment_counters
  update_counters(1)
end

#inversed_from(record)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 50

def inversed_from(record)
  replace_keys(record)
  super
end

#invertible_for?(record) ⇒ Boolean (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 169

def invertible_for?(record)
  inverse = inverse_reflection_for(record)
  inverse && (inverse.has_one? || inverse.klass.has_many_inversing)
end

#primary_key(klass) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 161

def primary_key(klass)
  reflection.association_primary_key(klass)
end

#replace(record) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 105

def replace(record)
  if record
    raise_on_type_mismatch!(record)
    set_inverse_instance(record)
    @updated = true
  elsif target
    remove_inverse_instance(target)
  end

  replace_keys(record, force: true)

  self.target = record
end

#replace_keys(record, force: false) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 143

def replace_keys(record, force: false)
  target_key_values = record ? ActiveRecord::Key.for(primary_key(record.class)).map { |col| record.read_attribute(col) } : []
  owner_key_values = foreign_key.map { |fk| owner.read_attribute(fk) }

  return if !force && owner_key_values == target_key_values

  owner_pk = ActiveRecord::Key.for(owner.class.primary_key)

  # Preserve shared primary key columns only if another foreign key
  # column can be cleared to disassociate the record.
  preserve_owner_pk = record.nil? && foreign_key.any? { |key| !owner_pk.include?(key) }

  foreign_key.each_with_index do |key, index|
    next if preserve_owner_pk && owner_pk.include?(key)
    owner.write_attribute(key, target_key_values[index])
  end
end

#reset

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 59

def reset
  super
  @updated = false
end

#stale_state (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 174

def stale_state
  values = foreign_key.map do |fk|
    owner.read_attribute(fk) { |n| owner.send(:missing_attribute, n, caller) }
  end
  foreign_key.composite? ? (values if values.any?) : values.first
end

#update_counters(by) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 119

def update_counters(by)
  if require_counter_update? && foreign_key_present?
    if target && !stale_target?
      target.increment!(reflection.counter_cache_column, by, touch: reflection.options[:touch])
    else
      update_counters_via_scope(klass, foreign_key.value_of(owner), by)
    end
  end
end

#update_counters_via_scope(klass, values, by) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/belongs_to_association.rb', line 129

def update_counters_via_scope(klass, values, by)
  primary_key = ActiveRecord::Key.for(primary_key(klass))
  scope = klass.all_queries_scope.where!(primary_key.where_hash(values))
  scope.update_counters(reflection.counter_cache_column => by, touch: reflection.options[:touch])
end