module ActiveRecord::NestedAttributes
def self.included(base)
def self.included(base) base.extend(ClassMethods) base.class_inheritable_accessor :nested_attributes_options, :instance_writer => false base.nested_attributes_options = {} end
def _destroy
destruction of this association.
used in conjunction with fields_for to build a form element for the
Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
def _destroy marked_for_destruction? end
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
{ :id => '2', :_destroy => true }
{ :name => 'John' },
{ :id => '1', :name => 'Peter' },
assign_nested_attributes_for_collection_association(:people, [
Also accepts an Array of attribute hashes:
for destruction.
person with the name `John', and mark the associatied Person with ID 2
Will update the name of the Person with ID 1, build a new associated
})
'3' => { :id => '2', :_destroy => true }
'2' => { :name => 'John' },
'1' => { :id => '1', :name => 'Peter' },
assign_nested_attributes_for_collection_association(:people, {
For example:
matched record for destruction.
value and a :_destroy key set to a truthy value will mark the
a new record for the association. Hashes with a matching :id
will update that record. Hashes without an :id value will build
Hashes with an :id value matching an existing associated record
Assigns the given attributes to the collection association.
def assign_nested_attributes_for_collection_association(association_name, attributes_collection) options = nested_attributes_options[association_name] unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array) raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" end if options[:limit] && attributes_collection.size > options[:limit] raise TooManyRecords, "Maximum #{options[:limit]} records are allowed. Got #{attributes_collection.size} records instead." end if attributes_collection.is_a? Hash attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes } end association = send(association_name) existing_records = if association.loaded? association.to_a else attribute_ids = attributes_collection.map {|a| a['id'] || a[:id] }.compact attribute_ids.present? ? association.all(:conditions => {association.primary_key => attribute_ids}) : [] end attributes_collection.each do |attributes| attributes = attributes.with_indifferent_access if attributes['id'].blank? unless reject_new_record?(association_name, attributes) association.build(attributes.except(*UNASSIGNABLE_KEYS)) end elsif existing_record = existing_records.detect { |record| record.id.to_s == attributes['id'].to_s } association.send(:add_record_to_target_with_callbacks, existing_record) unless association.loaded? assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) else raise_nested_attributes_record_not_found(association_name, attributes['id']) end end end
def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
update_only is true, and a :_destroy key set to a truthy value,
If the given attributes include a matching :id attribute, or
object exists. Otherwise a new record will be built.
modified. If update_only is true, a new record is only created when no
that matches the existing record’s id, then the existing record will be
If update_only is false and the given attributes include an :id
Assigns the given attributes to the association.
def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = nested_attributes_options[association_name] attributes = attributes.with_indifferent_access check_existing_record = (options[:update_only] || !attributes['id'].blank?) if check_existing_record && (record = send(association_name)) && (options[:update_only] || record.id.to_s == attributes['id'].to_s) assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) elsif attributes['id'] raise_nested_attributes_record_not_found(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) method = "build_#{association_name}" if respond_to?(method) send(method, attributes.except(*UNASSIGNABLE_KEYS)) else raise ArgumentError, "Cannot build association #{association_name}. Are you trying to build a polymorphic one-to-one association?" end end end
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
Updates a record with the +attributes+ or marks it for destruction if
def assign_to_or_mark_for_destruction(record, attributes, allow_destroy) if has_destroy_flag?(attributes) && allow_destroy record.mark_for_destruction else record.attributes = attributes.except(*UNASSIGNABLE_KEYS) end end
def call_reject_if(association_name, attributes)
def call_reject_if(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
def has_destroy_flag?(hash)
def has_destroy_flag?(hash) ConnectionAdapters::Column.value_to_boolean(hash['_destroy']) end
def raise_nested_attributes_record_not_found(association_name, record_id)
def raise_nested_attributes_record_not_found(association_name, record_id) reflection = self.class.reflect_on_association(association_name) raise RecordNotFound, "Couldn't find #{reflection.klass.name} with ID=#{record_id} for #{self.class.name} with ID=#{id}" end
def reject_new_record?(association_name, attributes)
has_destroy_flag? or if a :reject_if proc exists for this
Determines if a new record should be build by checking for
def reject_new_record?(association_name, attributes) has_destroy_flag?(attributes) || call_reject_if(association_name, attributes) end