123456789_123456789_123456789_123456789_123456789_

Class: ActiveRecord::Associations::HasManyAssociation

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::CollectionAssociation
Defined in: activerecord/lib/active_record/associations/has_many_association.rb

Overview

Active Record Has Many Association

This is the proxy that handles a has many association.

If the association has a :through option further specialization is provided by its child HasManyThroughAssociation.

Class Method Summary

Association - Inherited

Instance Attribute Summary

ForeignAssociation - Included

CollectionAssociation - Inherited

Association - Inherited

#collection?

Whether the association represent 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.

#find_target?,
#foreign_key_present?

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

#violates_strict_loading?

Instance Method Summary

ForeignAssociation - Included

#nullified_owner_attributes,
#set_owner_attributes

Sets the owner attributes on the given record.

CollectionAssociation - Inherited

#add_to_target, #build,
#concat

Add records to this association.

#delete

Removes records from this association calling before_remove and after_remove callbacks.

#delete_all

Removes all records from the association without calling callbacks on the associated records.

#destroy

Deletes the records and removes them from this association calling before_remove, after_remove, before_destroy and after_destroy callbacks.

#destroy_all

Destroy all the records from this association.

#find,
#ids_reader

Implements the ids reader method, e.g.

#ids_writer

Implements the ids writer method, e.g.

#include?, #load_target,
#reader

Implements the reader method, e.g.

#replace

Replace this collection with other_array.

#reset, #scope,
#size

Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn’t been loaded, and calling collection.size if it has.

#writer

Implements the writer method, e.g.

#_create_record, #callback, #callbacks_for, #concat_records, #delete_or_destroy,
#delete_records

Delete the given records from the association, using one of the methods :destroy, :delete_all or :nullify (or nil, in which case a default is used).

#find_by_scan

If the :inverse_of option has been specified, then #find scans the entire collection.

#include_in_memory?,
#insert_record

Do the relevant stuff to insert the given record into the association collection.

#merge_target_lists

We have some records loaded from the database (persisted) and some that are in-memory (memory).

#remove_records, #replace_common_records_in_memory, #replace_on_target, #replace_records, #transaction

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

This class inherits a constructor from ActiveRecord::Associations::Association

Instance Method Details

#_create_record(attributes) (private)

[ GitHub ]

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

def _create_record(attributes, *)
  if attributes.is_a?(Array)
    super
  else
    update_counter_if_success(super, 1)
  end
end

#concat_records(records) (private)

[ GitHub ]

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

def concat_records(records, *)
  update_counter_if_success(super, records.length)
end

#count_records (private)

Returns the number of records in this collection.

If the association has a counter cache it gets that value. Otherwise it will attempt to do a count via SQL, bounded to :limit if there’s one. Some configuration options like :group make it impossible to do an SQL count, in those cases the array count will be used.

That does not depend on whether the collection has already been loaded or not. The size method is the one that takes the loaded flag into account and delegates to count_records if needed.

If the collection is empty the target is set to an empty array and the loaded flag is set to true as well.

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 80

def count_records
  count = if reflection.has_active_cached_counter?
    owner.read_attribute(reflection.counter_cache_column).to_i
  else
    scope.count(:all)
  end

  # If there's nothing in the database, @target should only contain new
  # records or be an empty array. This is a documented side-effect of
  # the method that may avoid an extra SELECT.
  if count == 0
    target.select!(&:new_record?)
    loaded!
  end

  [association_scope.limit_value, count].compact.min
end

#delete_count(method, scope) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 112

def delete_count(method, scope)
  if method == :delete_all
    scope.delete_all
  else
    scope.update_all(nullified_owner_attributes)
  end
end

#delete_or_nullify_all_records(method) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 120

def delete_or_nullify_all_records(method)
  count = delete_count(method, scope)
  update_counter(-count)
  count
end

#delete_records(records, method) (private)

Deletes the records according to the :dependent option.

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 127

def delete_records(records, method)
  if method == :destroy
    records.each(&:destroy!)
    update_counter(-records.length) unless reflection.inverse_updates_counter_cache?
  else
    query_constraints = reflection.klass.composite_query_constraints_list
    values = records.map { |r| query_constraints.map { |col| r._read_attribute(col) } }
    scope = self.scope.where(query_constraints => values)
    update_counter(-delete_count(method, scope))
  end
end

#difference(a, b) (private)

[ GitHub ]

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

def difference(a, b)
  a - b
end

#handle_dependency

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 14

def handle_dependency
  case options[:dependent]
  when :restrict_with_exception
    raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?

  when :restrict_with_error
    unless empty?
      record = owner.class.human_attribute_name(reflection.name).downcase
      owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
      throw(:abort)
    end

  when :destroy
    # No point in executing the counter update since we're going to destroy the parent anyway
    load_target.each { |t| t.destroyed_by_association = reflection }
    destroy_all
  when :destroy_async
    load_target.each do |t|
      t.destroyed_by_association = reflection
    end

    unless target.empty?
      association_class = target.first.class
      if association_class.query_constraints_list
        primary_key_column = association_class.query_constraints_list
        ids = target.collect { |assoc| primary_key_column.map { |col| assoc.public_send(col) } }
      else
        primary_key_column = association_class.primary_key
        ids = target.collect { |assoc| assoc.public_send(primary_key_column) }
      end

      ids.each_slice(owner.class.destroy_association_async_batch_size || ids.size) do |ids_batch|
        enqueue_destroy_association(
          owner_model_name: owner.class.to_s,
          owner_id: owner.id,
          association_class: reflection.klass.to_s,
          association_ids: ids_batch,
          association_primary_key_column: primary_key_column,
          ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
        )
      end
    end
  else
    delete_all
  end
end

#insert_record(record, validate = true, raise = false)

[ GitHub ]

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

def insert_record(record, validate = true, raise = false)
  set_owner_attributes(record)
  super
end

#intersection(a, b) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 162

def intersection(a, b)
  a & b
end

#update_counter(difference, reflection = reflection()) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 98

def update_counter(difference, reflection = reflection())
  if reflection.has_cached_counter?
    owner.increment!(reflection.counter_cache_column, difference)
  end
end

#update_counter_if_success(saved_successfully, difference) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 151

def update_counter_if_success(saved_successfully, difference)
  if saved_successfully
    update_counter_in_memory(difference)
  end
  saved_successfully
end

#update_counter_in_memory(difference, reflection = reflection()) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/associations/has_many_association.rb', line 104

def update_counter_in_memory(difference, reflection = reflection())
  if reflection.counter_must_be_updated_by_has_many?
    counter = reflection.counter_cache_column
    owner.increment(counter, difference)
    owner.send(:"clear_#{counter}_change")
  end
end