module ActiveFedora::NestedAttributes
def _destroy
destruction of this association.
used in conjunction with fields_for to build a form element for the
Returns ActiveFedora::Base#marked_for_destruction? It's
def _destroy marked_for_destruction? end
def allow_destroy?(association_name)
def allow_destroy?(association_name) nested_attributes_options[association_name][:allow_destroy] end
def assign_nested_attributes_for_collection_association(association_name, attributes_collection)
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, got #{attributes_collection.class.name} (#{attributes_collection.inspect})" 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) Array(attributes_collection) else attributes_collection.sort_by { |i, _| i.to_i }.map { |_, attributes| attributes } end end association = send(association_name) existing_records = if association.loaded? association.target else attribute_ids = attributes_collection.map { |a| a['id'] || a[:id] }.compact attribute_ids.present? ? association.to_a.select { |x| attribute_ids.include?(x.id) } : [] end attributes_collection.each do |attributes| attributes = attributes.to_h if attributes.respond_to?(:permitted) 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) if !association.loaded? && !call_reject_if(association_name, attributes) unless call_reject_if(association_name, attributes) assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) end 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
record will be modified regardless of whether an :id is provided.
it will be replaced with a new record. If update_only is +true+ the existing
id, then the existing record will be modified. If no :id is provided
given attributes include an :id that matches the existing record's
the value of the update_only option. If update_only is +false+ and the
an associated record already exists, the method's behavior depends on
If an associated record does not yet exist, one will be instantiated. If
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.to_h if attributes.respond_to?(:permitted?) 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
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) record.attributes = attributes.except(*UNASSIGNABLE_KEYS) record.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy end
def call_reject_if(association_name, attributes)
The reject_if option is defined by +accepts_nested_attributes_for+.
rejected by calling the reject_if Symbol or Proc (if defined).
Determines if a record with the particular +attributes+ should be
def call_reject_if(association_name, attributes) return false if will_be_destroyed?(association_name, attributes) opts = nested_attributes_options[association_name] case callback = opts[:reject_if] when Symbol method(callback).arity == 0 ? send(callback) : send(callback, attributes) when Proc callback.call(attributes) end end
def check_record_limit!(limit, attributes_collection)
Raises TooManyRecords error if the attributes_collection is
number-like object (anything that can be compared with an integer).
records. It accepts limit in the form of symbol, proc, or
Takes in a limit and checks if the attributes_collection has too many
def check_record_limit!(limit, attributes_collection) return unless limit limit = case limit when Symbol send(limit) when Proc limit.call else limit end raise TooManyRecords, "Maximum #{limit} records are allowed. Got #{attributes_collection.size} records instead." if limit && attributes_collection.size > limit end
def has_destroy_flag?(hash)
def has_destroy_flag?(hash) Type::Boolean.new.cast(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).klass.name raise ObjectNotFoundError, "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 rejected by checking
def reject_new_record?(association_name, attributes) will_be_destroyed?(association_name, attributes) || call_reject_if(association_name, attributes) end
def will_be_destroyed?(association_name, attributes)
def will_be_destroyed?(association_name, attributes) allow_destroy?(association_name) && has_destroy_flag?(attributes) end