123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Associations::Preloader::Association

Do not use. This class is for internal use only.
Relationships & Source Files
Namespace Children
Classes:
Extension / Inclusion / Inheritance Descendants
Subclasses:
Inherits: Object
Defined in: activerecord/lib/active_record/associations/preloader/association.rb

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default) ⇒ Association

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 108

def initialize(klass, owners, reflection, preload_scope, reflection_scope, associate_by_default)
  @klass         = klass
  @owners        = owners.uniq(&:__id__)
  @reflection    = reflection
  @preload_scope = preload_scope
  @reflection_scope = reflection_scope
  @associate     = associate_by_default || !preload_scope || preload_scope.empty_scope?
  @model         = owners.first && owners.first.class
  @run = false
end

Class Method Details

.association_route_for(reflection, owner_class, klass)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 102

def self.association_route_for(reflection, owner_class, klass)
  reflection.association_route(origin_class: owner_class, destination_class: klass)
end

Instance Attribute Details

#klass (readonly)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 106

attr_reader :klass

#model (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 246

attr_reader :owners, :reflection, :preload_scope, :model

#owners (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 246

attr_reader :owners, :reflection, :preload_scope, :model

#preload_scope (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 246

attr_reader :owners, :reflection, :preload_scope, :model

#reflection (readonly, private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 246

attr_reader :owners, :reflection, :preload_scope, :model

#run? ⇒ Boolean (readonly)

[ GitHub ]

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

def run?
  @run
end

Instance Method Details

#associate_records_from_unscoped(unscoped_records)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 226

def associate_records_from_unscoped(unscoped_records)
  return if unscoped_records.nil? || unscoped_records.empty?
  return if !reflection_scope.empty_scope?
  return if preload_scope && !preload_scope.empty_scope?
  return if reflection.collection?

  unscoped_records.each do |record|
    owners = owners_by_key[destination_key_value(record)]
    owners&.each_with_index do |owner, i|
      association = owner.association(reflection.name)
      association.target = record

      if i == 0 # Set inverse on first owner
        association.set_inverse_instance(record)
      end
    end
  end
end

#associate_records_to_owner(owner, records) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 252

def associate_records_to_owner(owner, records)
  return if loaded?(owner)

  association = owner.association(reflection.name)

  if reflection.collection?
    not_persisted_records = association.target.reject(&:persisted?)
    association.target = records + not_persisted_records
  else
    association.target = records.first
  end
end

#association_route (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 248

def association_route
  @association_route ||= self.class.association_route_for(reflection, model, klass)
end

#build_scope (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 291

def build_scope
  scope = klass.scope_for_association
  fixed_values = destination_fixed_values
  scope.where!(fixed_values) unless fixed_values.empty?

  scope.merge!(reflection_scope) unless reflection_scope.empty_scope?

  if preload_scope && !preload_scope.empty_scope?
    scope.merge!(preload_scope)
  end

  cascade_strict_loading(scope)
end

#cascade_strict_loading(scope) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 305

def cascade_strict_loading(scope)
  preload_scope&.strict_loading_value ? scope.strict_loading : scope
end

#destination_fixed_values (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 287

def destination_fixed_values
  association_route.destination_fixed_values
end

#destination_key_name

The name of the key on the associated records

[ GitHub ]

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

def destination_key_name
  association_route.destination_key.name
end

#destination_key_value(record) (private)

Loaded records can be subclasses of klass with their own aliases.

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 271

def destination_key_value(record)
  if record.class.equal?(klass)
    association_route._destination_key._value_of(record, match_normalizers.last)
  else
    association_route.destination_key.value_of(record, match_normalizers.last)
  end
end

#future_classes

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 123

def future_classes
  if run?
    []
  else
    [@klass]
  end
end

#load_records(raw_records = nil)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 205

def load_records(raw_records = nil)
  # owners can be duplicated when a relation has a collection association join
  # #compare_by_identity makes such owners different hash keys
  @records_by_owner = {}.compare_by_identity
  raw_records ||= loader_query.records_for([self])
  @preloaded_records = raw_records.select do |record|
    assignments = false

    owners_by_key[destination_key_value(record)]&.each do |owner|
      entries = (@records_by_owner[owner] ||= [])

      if reflection.collection? || entries.empty?
        entries << record
        assignments = true
      end
    end

    assignments
  end
end

#loaded?(owner) ⇒ Boolean

[ GitHub ]

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

def loaded?(owner)
  owner.association(reflection.name).loaded?
end

#loader_query

[ GitHub ]

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

def loader_query
  LoaderQuery.new(scope, destination_key_name)
end

#match_normalizers (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 279

def match_normalizers
  @match_normalizers ||= association_route.match_normalizers(origin_class: model, destination_class: klass)
end

#origin_key_value(record) (private)

Owners are grouped so that each loader's owners share its route.

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 266

def origin_key_value(record)
  association_route._origin_key._value_of(record, match_normalizers.first)
end

#owners_by_key

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 173

def owners_by_key
  @owners_by_key ||= begin
    route = association_route
    owners.each_with_object({}) do |owner, result|
      next unless route.origin_reference_complete?(owner)

      (result[origin_key_value(owner)] ||= []) << owner
    end
  end
end

#preloaded_records

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 158

def preloaded_records
  load_records unless defined?(@preloaded_records)

  @preloaded_records
end

#records_by_owner

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 152

def records_by_owner
  load_records unless defined?(@records_by_owner)

  @records_by_owner
end

#reflection_scope (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 283

def reflection_scope
  @reflection_scope ||= reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(klass.unscoped, &:merge!)
end

#run (readonly)

[ GitHub ]

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

def run
  return self if run?
  @run = true

  records = records_by_owner

  owners.each do |owner|
    associate_records_to_owner(owner, records[owner] || [])
  end if @associate

  self
end

#runnable_loaders

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 131

def runnable_loaders
  [self]
end

#scope

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 192

def scope
  @scope ||= build_scope
end

#set_inverse(record)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 196

def set_inverse(record)
  if owners = owners_by_key[destination_key_value(record)]
    # Processing only the first owner
    # because the record is modified but not an owner
    association = owners.first.association(reflection.name)
    association.set_inverse_instance(record)
  end
end

#table_name

[ GitHub ]

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

def table_name
  @klass.table_name
end

#target_for(owner)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/preloader/association.rb', line 188

def target_for(owner)
  Array.wrap(owner.association(reflection.name).target)
end