123456789_123456789_123456789_123456789_123456789_

Module: ActiveRecord::NestedAttributes

Do not use. This module is for internal use only.
Relationships & Source Files
Namespace Children
Modules:
Exceptions:
Extension / Inclusion / Inheritance Descendants
Included In:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Defined in: activerecord/lib/active_record/nested_attributes.rb

Constant Summary

Class Method Summary

::ActiveSupport::Concern - Extended

class_methods

Define class methods from given block.

included

Evaluate given block in context of base class, so that you can write class macros here.

prepended

Evaluate given block in context of base class, so that you can write class macros here.

append_features, prepend_features

Instance Method Summary

DSL Calls

included

[ GitHub ]


14
15
16
# File 'activerecord/lib/active_record/nested_attributes.rb', line 14

included do
  class_attribute :nested_attributes_options, instance_writer: false, default: {}
end

Instance Method Details

#_destroy

Returns AutosaveAssociation#marked_for_destruction? It’s used in conjunction with fields_for to build a form element for the destruction of this association.

See ActionView::Helpers::FormHelper#fields_for for more info.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 401

def _destroy
  marked_for_destruction?
end

#allow_destroy?(association_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 614

def allow_destroy?(association_name)
  nested_attributes_options[association_name][:allow_destroy]
end

#assign_nested_attributes_for_collection_association(association_name, attributes_collection) (private)

Assigns the given attributes to the collection association.

Hashes with an :id value matching an existing associated record will update that record. Hashes without an :id value will build a new record for the association. Hashes with a matching :id value and a :_destroy key set to a truthy value will mark the matched record for destruction.

For example:

assign_nested_attributes_for_collection_association(:people, {
  '1' => { id: '1', name: 'Peter' },
  '2' => { name: 'John' },
  '3' => { id: '2', _destroy: true }
})

Will update the name of the Person with ID 1, build a new associated person with the name ‘John’, and mark the associated Person with ID 2 for destruction.

Also accepts an ::Array of attribute hashes:

assign_nested_attributes_for_collection_association(:people, [
  { id: '1', name: 'Peter' },
  { name: 'John' },
  { id: '2', _destroy: true }
])
[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 487

def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
  options = nested_attributes_options[association_name]
  if attributes_collection.respond_to?(:permitted?)
    attributes_collection = attributes_collection.to_h
  end

  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
    raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
  end

  check_record_limit!(options[:limit], attributes_collection)

  if attributes_collection.is_a? Hash
    keys = attributes_collection.keys
    attributes_collection = if keys.include?("id") || keys.include?(:id)
      [attributes_collection]
    else
      attributes_collection.values
    end
  end

  association = association(association_name)

  existing_records = if association.loaded?
    association.target
  else
    attribute_ids = attributes_collection.filter_map { |a| a["id"] || a[:id] }
    attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
  end

  records = attributes_collection.map do |attributes|
    if attributes.respond_to?(:permitted?)
      attributes = attributes.to_h
    end
    attributes = attributes.with_indifferent_access

    if attributes["id"].blank?
      unless reject_new_record?(association_name, attributes)
        association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
      end
    elsif existing_record = find_record_by_id(association.klass, existing_records, attributes["id"])
      unless call_reject_if(association_name, attributes)
        # Make sure we are operating on the actual object which is in the association's
        # proxy_target array (either by finding it, or adding it if not found)
        # Take into account that the proxy_target may have changed due to callbacks
        target_record = find_record_by_id(association.klass, association.target, attributes["id"])
        if target_record
          existing_record = target_record
        else
          association.add_to_target(existing_record, skip_callbacks: true)
        end

        assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
        existing_record
      end
    else
      raise_nested_attributes_record_not_found!(association_name, attributes["id"])
    end
  end

  association.nested_attributes_target = records
end

#assign_nested_attributes_for_one_to_one_association(association_name, attributes) (private)

Assigns the given attributes to the association.

If an associated record does not yet exist, one will be instantiated. If an associated record already exists, the method’s behavior depends on the value of the update_only option. If update_only is false and the given attributes include an :id that matches the existing record’s id, then the existing record will be modified. If no :id is provided it will be replaced with a new record. If update_only is true the existing record will be modified regardless of whether an :id is provided.

If the given attributes include a matching :id attribute, or update_only is true, and a :_destroy key set to a truthy value, then the existing record will be marked for destruction.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 423

def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
  if attributes.respond_to?(:permitted?)
    attributes = attributes.to_h
  end

  unless attributes.is_a?(Hash)
    raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}"
  end

  options = nested_attributes_options[association_name]
  attributes = attributes.with_indifferent_access
  existing_record = send(association_name)

  if (options[:update_only] || !attributes["id"].blank?) && existing_record &&
      (options[:update_only] || existing_record.id.to_s == attributes["id"].to_s)
    assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes)

  elsif attributes["id"].present?
    raise_nested_attributes_record_not_found!(association_name, attributes["id"])

  elsif !reject_new_record?(association_name, attributes)
    assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS)

    if existing_record && existing_record.new_record?
      existing_record.assign_attributes(assignable_attributes)
      association(association_name).initialize_attributes(existing_record)
    else
      method = :"build_#{association_name}"
      if respond_to?(method)
        send(method, assignable_attributes)
      else
        raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?"
      end
    end
  end
end

#assign_to_or_mark_for_destruction(record, attributes, allow_destroy) (private)

Updates a record with the attributes or marks it for destruction if allow_destroy is true and has_destroy_flag? returns true.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 576

def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
  record.assign_attributes(attributes.except(*UNASSIGNABLE_KEYS))
  record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
end

#call_reject_if(association_name, attributes) (private)

Determines if a record with the particular attributes should be rejected by calling the reject_if ::Symbol or Proc (if defined). The reject_if option is defined by accepts_nested_attributes_for.

Returns false if there is a destroy_flag on the attributes.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 598

def call_reject_if(association_name, attributes)
  return false if will_be_destroyed?(association_name, attributes)

  case callback = nested_attributes_options[association_name][:reject_if]
  when Symbol
    method(callback).arity == 0 ? send(callback) : send(callback, attributes)
  when Proc
    callback.call(attributes)
  end
end

#check_record_limit!(limit, attributes_collection) (private)

Takes in a limit and checks if the attributes_collection has too many records. It accepts limit in the form of symbol, proc, or number-like object (anything that can be compared with an integer).

Raises TooManyRecords error if the attributes_collection is larger than the limit.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 556

def check_record_limit!(limit, attributes_collection)
  if limit
    limit = \
      case limit
      when Symbol
        send(limit)
      when Proc
        limit.call
      else
        limit
      end

    if limit && attributes_collection.size > limit
      raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead."
    end
  end
end

#find_record_by_id(klass, records, id) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 624

def find_record_by_id(klass, records, id)
  if klass.composite_primary_key?
    id = Array(id).map(&:to_s)
    records.find { |record| Array(record.id).map(&:to_s) == id }
  else
    records.find { |record| record.id.to_s == id.to_s }
  end
end

#has_destroy_flag?(hash) ⇒ Boolean (private)

Determines if a hash contains a truthy _destroy key.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 582

def has_destroy_flag?(hash)
  Type::Boolean.new.cast(hash["_destroy"])
end

#raise_nested_attributes_record_not_found!(association_name, record_id) (private)

Raises:

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 618

def raise_nested_attributes_record_not_found!(association_name, record_id)
  model = self.class._reflect_on_association(association_name).klass.name
  raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
                           model, "id", record_id)
end

#reject_new_record?(association_name, attributes) ⇒ Boolean (private)

Determines if a new record should be rejected by checking has_destroy_flag? or if a :reject_if proc exists for this association and evaluates to true.

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 589

def reject_new_record?(association_name, attributes)
  will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes)
end

#will_be_destroyed?(association_name, attributes) ⇒ Boolean (private)

Only take into account the destroy flag if :allow_destroy is true

[ GitHub ]

  
# File 'activerecord/lib/active_record/nested_attributes.rb', line 610

def will_be_destroyed?(association_name, attributes)
  allow_destroy?(association_name) && has_destroy_flag?(attributes)
end