class ActiveRecord::Associations::CollectionAssociation

:nodoc:
load_target and the loaded flag are your friends.
If you need to work on all current children, new and existing records,
collection because new records may have been added to the target, etc.
If you look directly to the database you cannot assume that’s the entire
non-empty and still lack children waiting to be read from the database.
ones created with build are added to the target. So, the target may be
does not fetch records from the database until it needs them, but new
You need to be careful with assumptions regarding the target: The proxy
+:through association+ option.
defined by has_and_belongs_to_many, has_many or has_many with
CollectionAssociation class provides common methods to the collections
HasManyThroughAssociation + ThroughAssociation => has_many :through
HasManyAssociation => has_many
CollectionAssociation:
collections. See the class hierarchy in Association.
ease the implementation of association proxies that represent
CollectionAssociation is an abstract class that provides common stuff to
= Active Record Association Collection

def add_to_target(record, skip_callbacks = false)

def add_to_target(record, skip_callbacks = false)
  callback(:before_add, record) unless skip_callbacks
  yield(record) if block_given?
  if association_scope.distinct_value && index = @target.index(record)
    @target[index] = record
  else
    @target << record
  end
  callback(:after_add, record) unless skip_callbacks
  set_inverse_instance(record)
  record
end

def any?

Equivalent to +!collection.empty?+.
Returns true if the collections is not empty.
def any?
  if block_given?
    load_target.any? { |*block_args| yield(*block_args) }
  else
    !empty?
  end
end

def build(attributes = {}, &block)

def build(attributes = {}, &block)
  if attributes.is_a?(Array)
    attributes.collect { |attr| build(attr, &block) }
  else
    add_to_target(build_record(attributes)) do |record|
      yield(record) if block_given?
    end
  end
end

def callback(method, record)

def callback(method, record)
  callbacks_for(method).each do |callback|
    callback.call(method, owner, record)
  end
end

def callbacks_for(callback_name)

def callbacks_for(callback_name)
  full_callback_name = "#{callback_name}_for_#{reflection.name}"
  owner.class.send(full_callback_name)
end

def concat(*records)

+push+ and +concat+ behave identically.
be chained. Since << flattens its argument list and inserts each record,
Add +records+ to this association. Returns +self+ so method calls may
def concat(*records)
  load_target if owner.new_record?
  if owner.new_record?
    concat_records(records)
  else
    transaction { concat_records(records) }
  end
end

def concat_records(records, should_raise = false)

def concat_records(records, should_raise = false)
  result = true
  records.flatten.each do |record|
    raise_on_type_mismatch!(record)
    add_to_target(record) do |rec|
      result &&= insert_record(rec, true, should_raise) unless owner.new_record?
    end
  end
  result && records
end

def count(column_name = nil, count_options = {})

scope to the target class's +count+.
Count all records using SQL. Construct options and pass them with
def count(column_name = nil, count_options = {})
  # TODO: Remove count_options argument as soon we remove support to
  # activerecord-deprecated_finders.
  column_name, count_options = nil, column_name if column_name.is_a?(Hash)
  relation = scope
  if association_scope.distinct_value
    # This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
    column_name ||= reflection.klass.primary_key
    relation = relation.distinct
  end
  value = relation.count(column_name)
  limit  = options[:limit]
  offset = options[:offset]
  if limit || offset
    [ [value - offset.to_i, 0].max, limit.to_i ].min
  else
    value
  end
end

def create(attributes = {}, &block)

def create(attributes = {}, &block)
  create_record(attributes, &block)
end

def create!(attributes = {}, &block)

def create!(attributes = {}, &block)
  create_record(attributes, true, &block)
end

def create_record(attributes, raise = false, &block)

def create_record(attributes, raise = false, &block)
  unless owner.persisted?
    raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
  end
  if attributes.is_a?(Array)
    attributes.collect { |attr| create_record(attr, raise, &block) }
  else
    transaction do
      add_to_target(build_record(attributes)) do |record|
        yield(record) if block_given?
        insert_record(record, true, raise)
      end
    end
  end
end

def create_scope

def create_scope
  scope.scope_for_create.stringify_keys
end

def delete(*records)

+delete_records+. They are in any case removed from the collection.
are actually removed from the database, that depends precisely on
provided by descendants. Note this method does not imply the records
This method is abstract in the sense that +delete_records+ has to be

+after_remove+ callbacks.
Removes +records+ from this association calling +before_remove+ and
def delete(*records)
  _options = records.extract_options!
  dependent = _options[:dependent] || options[:dependent]
  if records.first == :all
    if loaded? || dependent == :destroy
      delete_or_destroy(load_target, dependent)
    else
      delete_records(:all, dependent)
    end
  else
    records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
    delete_or_destroy(records, dependent)
  end
end

def delete_all(dependent = nil)

See delete for more info.

@author.books.delete_all(:delete_all)
@author.books.delete_all(:nullify)

Example:

You can force a particular deletion strategy by passing a parameter.

deletion strategy for the association is applied.
if the `:dependent` value is `:destroy` then in that case the `:delete_all`
on the associated records. It honors the `:dependent` option. However
Removes all records from the association without calling callbacks
def delete_all(dependent = nil)
  if dependent.present? && ![:nullify, :delete_all].include?(dependent)
    raise ArgumentError, "Valid values are :nullify or :delete_all"
  end
  dependent = if dependent.present?
                dependent
              elsif options[:dependent] == :destroy
                :delete_all
              else
                options[:dependent]
              end
  delete(:all, dependent: dependent).tap do
    reset
    loaded!
  end
end

def delete_or_destroy(records, method)

def delete_or_destroy(records, method)
  records = records.flatten
  records.each { |record| raise_on_type_mismatch!(record) }
  existing_records = records.reject { |r| r.new_record? }
  if existing_records.empty?
    remove_records(existing_records, records, method)
  else
    transaction { remove_records(existing_records, records, method) }
  end
end

def delete_records(records, method)

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

def destroy(*records)

+:dependent+ option.
Note that this method removes records from the database ignoring the

+before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
Deletes the +records+ and removes them from this association calling
def destroy(*records)
  records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
  delete_or_destroy(records, :destroy)
end

def destroy_all

See destroy for more info.

Destroy all the records from this association.
def destroy_all
  destroy(load_target).tap do
    reset
    loaded!
  end
end

def distinct

def distinct
  seen = {}
  load_target.find_all do |record|
    seen[record.id] = true unless seen.key?(record.id)
  end
end

def empty?

check collection.length.zero?.
loaded and you are going to fetch the records anyway it is better to
collection.exists?. If the collection has not already been
collection has not been loaded, it is equivalent to
it is equivalent to collection.size.zero?. If the
If the collection has been loaded

Returns true if the collection is empty.
def empty?
  if loaded?
    size.zero?
  else
    @target.blank? && !scope.exists?
  end
end

def fetch_first_nth_or_last_using_find?(args)

* target contains new or changed record(s)
* owner is new record
* target already loaded
Otherwise, go to the database only if none of the following are true:

If the args is just a non-empty options hash, go to the database.

the database, or by getting the target, and then taking the first/last item from that?
Should we deal with assoc.first or assoc.last by issuing an independent query to
def fetch_first_nth_or_last_using_find?(args)
  if args.first.is_a?(Hash)
    true
  else
    !(loaded? ||
      owner.new_record? ||
      target.any? { |record| record.new_record? || record.changed? })
  end
end

def fifth(*args)

def fifth(*args)
  first_nth_or_last(:fifth, *args)
end

def find(*args)

def find(*args)
  if block_given?
    load_target.find(*args) { |*block_args| yield(*block_args) }
  else
    if options[:inverse_of] && loaded?
      args_flatten = args.flatten
      raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
      result = find_by_scan(*args)
      result_size = Array(result).size
      if !result || result_size != args_flatten.size
        scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
      else
        result
      end
    else
      scope.find(*args)
    end
  end
end

def find_by_scan(*args)

specified, then #find scans the entire collection.
If the :inverse_of option has been
def find_by_scan(*args)
  expects_array = args.first.kind_of?(Array)
  ids           = args.flatten.compact.map{ |arg| arg.to_s }.uniq
  if ids.size == 1
    id = ids.first
    record = load_target.detect { |r| id == r.id.to_s }
    expects_array ? [ record ] : record
  else
    load_target.select { |r| ids.include?(r.id.to_s) }
  end
end

def find_target

def find_target
  records = scope.to_a
  records.each { |record| set_inverse_instance(record) }
  records
end

def first(*args)

def first(*args)
  first_nth_or_last(:first, *args)
end

def first_nth_or_last(type, *args)

Fetches the first/last using SQL if possible, otherwise from the target array.
def first_nth_or_last(type, *args)
  args.shift if args.first.is_a?(Hash) && args.first.empty?
  collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
  collection.send(type, *args).tap do |record|
    set_inverse_instance record if record.is_a? ActiveRecord::Base
  end
end

def forty_two(*args)

def forty_two(*args)
  first_nth_or_last(:forty_two, *args)
end

def fourth(*args)

def fourth(*args)
  first_nth_or_last(:fourth, *args)
end

def ids_reader

Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
def ids_reader
  if loaded?
    load_target.map do |record|
      record.send(reflection.association_primary_key)
    end
  else
    column  = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
    scope.pluck(column)
  end
end

def ids_writer(ids)

Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
  pk_column = reflection.primary_key_column
  ids = Array(ids).reject { |id| id.blank? }
  ids.map! { |i| pk_column.type_cast(i) }
  replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
end

def include?(record)

def include?(record)
  if record.is_a?(reflection.klass)
    if record.new_record?
      include_in_memory?(record)
    else
      loaded? ? target.include?(record) : scope.exists?(record)
    end
  else
    false
  end
end

def include_in_memory?(record)

def include_in_memory?(record)
  if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
    assoc = owner.association(reflection.through_reflection.name)
    assoc.reader.any? { |source|
      target = source.send(reflection.source_reflection.name)
      target.respond_to?(:include?) ? target.include?(record) : target == record
    } || target.include?(record)
  else
    target.include?(record)
  end
end

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

Do the relevant stuff to insert the given record into the association collection.
def insert_record(record, validate = true, raise = false)
  raise NotImplementedError
end

def last(*args)

def last(*args)
  first_nth_or_last(:last, *args)
end

def length

method will take one less query. Otherwise +size+ is more efficient.
equivalent. If not and you are going to need the records anyway this
If the collection has been already loaded +length+ and +size+ are

Returns the size of the collection calling +size+ on the target.
def length
  load_target.size
end

def load_target

def load_target
  if find_target?
    @target = merge_target_lists(find_target, target)
  end
  loaded!
  target
end

def many?

Equivalent to +collection.size > 1+.
Returns true if the collection has more than 1 record.
def many?
  if block_given?
    load_target.many? { |*block_args| yield(*block_args) }
  else
    size > 1
  end
end

def merge_target_lists(persisted, memory)

* Otherwise, attributes should have the value found in the database
* Any changes made to attributes on objects in the memory array are to be preserved
* The order of the persisted array is to be preserved
* The final array must not have duplicates

So the task of this method is to merge them according to the following rules:

and in the memory array.
in-memory (memory). The same record may be represented in the persisted array
We have some records loaded from the database (persisted) and some that are
def merge_target_lists(persisted, memory)
  return persisted if memory.empty?
  return memory    if persisted.empty?
  persisted.map! do |record|
    if mem_record = memory.delete(record)
      ((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
        mem_record[name] = record[name]
      end
      mem_record
    else
      record
    end
  end
  persisted + memory
end

def null_scope?

def null_scope?
  owner.new_record? && !foreign_key_present?
end

def reader(force_reload = false)

Implements the reader method, e.g. foo.items for Foo.has_many :items
def reader(force_reload = false)
  if force_reload
    klass.uncached { reload }
  elsif stale_target?
    reload
  end
  @proxy ||= CollectionProxy.create(klass, self)
end

def remove_records(existing_records, records, method)

def remove_records(existing_records, records, method)
  records.each { |record| callback(:before_remove, record) }
  delete_records(existing_records, method) if existing_records.any?
  records.each { |record| target.delete(record) }
  records.each { |record| callback(:after_remove, record) }
end

def replace(other_array)

and delete/add only records that have changed.
Replace this collection with +other_array+. This will perform a diff
def replace(other_array)
  other_array.each { |val| raise_on_type_mismatch!(val) }
  original_target = load_target.dup
  if owner.new_record?
    replace_records(other_array, original_target)
  else
    transaction { replace_records(other_array, original_target) }
  end
end

def replace_records(new_target, original_target)

def replace_records(new_target, original_target)
  delete(target - new_target)
  unless concat(new_target - target)
    @target = original_target
    raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
                          "new records could not be saved."
  end
  target
end

def reset

def reset
  super
  @target = []
end

def scope(opts = {})

def scope(opts = {})
  scope = super()
  scope.none! if opts.fetch(:nullify, true) && null_scope?
  scope
end

def second(*args)

def second(*args)
  first_nth_or_last(:second, *args)
end

def select(*fields)

def select(*fields)
  if block_given?
    load_target.select.each { |e| yield e }
  else
    scope.select(*fields)
  end
end

def size

+count_records+, which is a method descendants have to provide.
This method is abstract in the sense that it relies on

+length+ will take one less query. Otherwise +size+ is more efficient.
equivalent. If not and you are going to need the records anyway
If the collection has been already loaded +size+ and +length+ are

collection.size if it has.
query if the collection hasn't been loaded, and calling
Returns the size of the collection by executing a SELECT COUNT(*)
def size
  if !find_target? || loaded?
    if association_scope.distinct_value
      target.uniq.size
    else
      target.size
    end
  elsif !loaded? && !association_scope.group_values.empty?
    load_target.size
  elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
    unsaved_records = target.select { |r| r.new_record? }
    unsaved_records.size + count_records
  else
    count_records
  end
end

def third(*args)

def third(*args)
  first_nth_or_last(:third, *args)
end

def transaction(*args)

end
# same effect as calling Book.transaction
Author.first.books.transaction do

end
has_many :books
class Author < ActiveRecord::Base

Starts a transaction in the association class's database connection.
def transaction(*args)
  reflection.klass.transaction(*args) do
    yield
  end
end

def writer(records)

Implements the writer method, e.g. foo.items= for Foo.has_many :items
def writer(records)
  replace(records)
end