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

Association - Inherited

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,
#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.

#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?,
#raise_on_type_mismatch!

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

#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

The foreign key and type are physical names for the owner's class, for use with the non-resolving attribute APIs and dirty tracking.

[ GitHub ]

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

def initialize(owner, reflection)
  super
  @foreign_key = reflection.association_route(origin_class: owner.class)._reference_origin_key
  @foreign_type = nil
  if reflection.polymorphic?
    @foreign_type = owner.resolve_alias(reflection.foreign_type)
  end
end

Instance Attribute Details

#find_target? ⇒ Boolean (readonly, private)

[ GitHub ]

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

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

#foreign_key (readonly)

[ GitHub ]

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

attr_reader :foreign_key, :foreign_type

#foreign_key_partially_overlaps_primary_key? ⇒ Boolean (readonly, private)

[ GitHub ]

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

def foreign_key_partially_overlaps_primary_key?
  primary_key = owner.class.primary_key_definition
  shared_count = foreign_key.count { |key| primary_key.include?(key) }
  shared_count > 0 && shared_count < foreign_key.size
end

#foreign_key_present? ⇒ Boolean (readonly, private)

[ GitHub ]

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

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

#foreign_type (readonly)

[ GitHub ]

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

attr_reader :foreign_key, :foreign_type

#require_counter_update? ⇒ Boolean (readonly, private)

[ GitHub ]

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

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 112

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 104

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 108

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 75

def updated?
  @updated
end

Instance Method Details

#association_route(record = nil)

[ GitHub ]

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

def association_route(record = nil)
  reflection.association_route(origin_class: owner.class, destination_class: record ? record.class : klass)
end

#clear_reference_with_shared_primary_key(force:) (private)

[ GitHub ]

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

def clear_reference_with_shared_primary_key(force:)
  reflection.association_route(origin_class: owner.class)
    .clear_reference(owner, preserve: owner.class.primary_key_definition, force: force)
end

#decrement_counters

[ GitHub ]

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

def decrement_counters
  update_counters(-1)
end

#decrement_counters_before_last_save

[ GitHub ]

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

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
  foreign_key_was = foreign_key.map_value { |key| owner.attribute_before_last_save(key) }
  foreign_key_was = nil if foreign_key.composite? && !foreign_key_was.all?

  if foreign_key_was && model_was < ActiveRecord::Base
    route = reflection.association_route(origin_class: owner.class, destination_class: model_was)
    origin_key_was = route._origin_key.map_value { |key| owner.attribute_before_last_save(key) }
    update_counters_via_scope(model_was, origin_key_was, -1, route)
  end
end

#default(&block)

[ GitHub ]

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

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

#destroy_association_async_columns(key) (private)

[ GitHub ]

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

def destroy_association_async_columns(key)
  declared_aliases = klass.attribute_aliases
  target_aliases = target.class.attribute_aliases
  key.map_value do |column|
    # The job queries the declared class, whose aliases may differ from
    # the assigned target's. In that case, pass the target's physical column.
    physical = target_aliases[column] || column
    (declared_aliases[column] || column) == physical ? column : physical
  end
end

#destroy_association_async_id(route) (private)

Prefer the target's persisted values, falling back to the owner for values unavailable after a partial select.

[ GitHub ]

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

def destroy_association_async_id(route)
  origin_columns = route._origin_key.columns
  reference_destination_key = route._reference_destination_key
  route._destination_key.map_value.with_index do |destination_column, index|
    origin_value = owner._read_attr(origin_columns[index])
    if target._has_attribute?(destination_column)
      destination_value = target.attribute_in_database(destination_column)
      # An unselected primary key can be present as nil. Use the owner's
      # reference for that case, but preserve nil query-constraint values.
      if !destination_value.nil? || !reference_destination_key.include?(destination_column)
        destination_value
      else
        origin_value
      end
    else
      origin_value
    end
  end
end

#handle_dependency

[ GitHub ]

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

def handle_dependency
  return unless load_target

  case options[:dependent]
  when :destroy
    raise ActiveRecord::Rollback unless target.destroy
  when :destroy_async
    route = association_route(target)
    destination_key = route.destination_key
    id = destroy_association_async_id(route)
    if !destination_key.composite? && id.is_a?(Array)
      # The job queries a list of ids, so use tuple syntax to keep this
      # Array-valued id together as one value.
      destination_key = ActiveRecord::Key.for(destination_key.columns)
      id = [id]
    end
    association_class = if reflection.polymorphic?
      owner.public_send(foreign_type)
    else
      klass
    end

    enqueue_destroy_association(
      owner_model_name: owner.class.to_s,
      owner_id: owner.id,
      association_class: association_class.to_s,
      association_ids: [id],
      association_primary_key_column: destroy_association_async_columns(destination_key),
      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 83

def increment_counters
  update_counters(1)
end

#inversed_from(record)

[ GitHub ]

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

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 212

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

#replace(record) (private)

[ GitHub ]

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

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 188

def replace_keys(record, force: false)
  if record.nil? && foreign_key_partially_overlaps_primary_key?
    clear_reference_with_shared_primary_key(force: force)
  else
    route = reflection.association_route(origin_class: owner.class, destination_class: record&.class)
    route.write(owner, record, force: force)
  end
end

#reset

[ GitHub ]

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

def reset
  super
  @updated = false
end

#stale_state (private)

[ GitHub ]

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

def stale_state
  value = foreign_key.map_value do |fk|
    owner._read_attr(fk)
  end
  foreign_key.composite? ? (value if value.any?) : value
end

#update_counters(by) (private)

[ GitHub ]

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

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
      route = association_route
      update_counters_via_scope(klass, route._origin_key._value_of(owner), by, route)
    end
  end
end

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

[ GitHub ]

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

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