Module: ActiveRecord::NestedAttributes
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Exceptions:
| |
Extension / Inclusion / Inheritance Descendants | |
Included In:
| |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
::ActiveSupport::Concern
|
|
Defined in: | activerecord/lib/active_record/nested_attributes.rb |
Constant Summary
-
UNASSIGNABLE_KEYS =
Attribute hash keys that should not be assigned as normal attributes. These hash keys are nested attributes implementation details.
%w( id _destroy )
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
-
#_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.
- #allow_destroy?(association_name) ⇒ Boolean private
-
#assign_nested_attributes_for_collection_association(association_name, attributes_collection)
private
Assigns the given attributes to the collection association.
-
#assign_nested_attributes_for_one_to_one_association(association_name, attributes)
private
Assigns the given attributes to the association.
-
#assign_to_or_mark_for_destruction(record, attributes, allow_destroy)
private
Updates a record with the
attributes
or marks it for destruction ifallow_destroy
istrue
and has_destroy_flag? returnstrue
. -
#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). -
#check_record_limit!(limit, attributes_collection)
private
Takes in a limit and checks if the attributes_collection has too many records.
- #find_record_by_id(klass, records, id) private
-
#has_destroy_flag?(hash) ⇒ Boolean
private
Determines if a hash contains a truthy _destroy key.
- #raise_nested_attributes_record_not_found!(association_name, record_id) private
-
#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 totrue
. -
#will_be_destroyed?(association_name, attributes) ⇒ Boolean
private
Only take into account the destroy flag if
:allow_destroy
is true.
DSL Calls
included
[ GitHub ]14 15 16
# File 'activerecord/lib/active_record/nested_attributes.rb', line 14
included do class_attribute :, 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.
# File 'activerecord/lib/active_record/nested_attributes.rb', line 401
def _destroy marked_for_destruction? end
#allow_destroy?(association_name) ⇒ Boolean
(private)
# File 'activerecord/lib/active_record/nested_attributes.rb', line 614
def allow_destroy?(association_name) [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 }
])
# File 'activerecord/lib/active_record/nested_attributes.rb', line 487
def assign_nested_attributes_for_collection_association(association_name, attributes_collection) = [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!( [: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, [: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.
# 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 = [association_name] attributes = attributes.with_indifferent_access existing_record = send(association_name) if ( [:update_only] || !attributes["id"].blank?) && existing_record && ( [:update_only] || existing_record.id.to_s == attributes["id"].to_s) assign_to_or_mark_for_destruction(existing_record, attributes, [: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
.
# 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.
# 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 = [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.
# 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 ]
#has_destroy_flag?(hash) ⇒ Boolean
(private)
Determines if a hash contains a truthy _destroy key.
#raise_nested_attributes_record_not_found!(association_name, record_id) (private)
# 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
.
# 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
# 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