module ActiveFedora::AutosaveAssociation

def associated_records_to_validate_or_save(association, new_record, autosave)

unless the parent is/was a new record itself.
or saved. If +autosave+ is +false+ only new records will be returned,
Returns the record for an association collection that should be validated
def associated_records_to_validate_or_save(association, new_record, autosave)
  if new_record
    association&.target
  elsif autosave
    association.target.find_all(&:changed_for_autosave?)
  else
    association.target.find_all(&:new_record?)
  end
end

def association_valid?(reflection, record)

enabled records if they're marked_for_destruction? or destroyed.
the parent, self, if it wasn't. Skips any :autosave
Returns whether or not the association is valid and applies any errors to
def association_valid?(reflection, record)
  return true if record.destroyed? || record.marked_for_destruction?
  unless valid = record.valid?
    if reflection.options[:autosave]
      record.errors.messages.each do |attribute, messages|
        attribute = "#{reflection.name}.#{attribute}"
        messages.map { |message| errors.add(attribute, message: message) }
      end
    else
      errors.add(reflection.name)
    end
  end
  valid
end

def before_save_collection_association

association whether or not the parent was a new record before saving.
Is used as a before_save callback to check while saving a collection
def before_save_collection_association
  @new_record_before_save = new_record?
  true
end

def changed_for_autosave?

any of its nested autosave associations are likewise changed)
Returns whether or not this record has been changed in any way (including whether
def changed_for_autosave?
  new_record? || changed? || marked_for_destruction? || nested_records_changed_for_autosave?
end

def destroyed_by_association

Used to avoid updating the counter cache unnecessarily.

Returns the association for the parent being destroyed.
def destroyed_by_association
  @destroyed_by_association
end

def destroyed_by_association=(reflection)

record in the process.
Records the association that is being destroyed and destroying this
def destroyed_by_association=(reflection)
  @destroyed_by_association = reflection
end

def mark_for_destruction

Only useful if the :autosave option on the parent is enabled for this associated model.

when parent.save is called.
This does _not_ actually destroy the record instantly, rather child record will be destroyed
Marks this record to be destroyed as part of the parents save transaction.
def mark_for_destruction
  @marked_for_destruction = true
end

def marked_for_destruction?

Only useful if the :autosave option on the parent is enabled for this associated model.

Returns whether or not this record will be destroyed as part of the parents save transaction.
def marked_for_destruction?
  @marked_for_destruction
end

def nested_records_changed_for_autosave?

any new ones), and return true if is changed for autosave
go through nested autosave associations that are loaded in memory (without loading
def nested_records_changed_for_autosave?
  self.class.reflect_on_all_autosave_associations.any? do |reflection|
    association = association_instance_get(reflection.name)
    association && Array(association.target).any?(&:changed_for_autosave?)
  end
end

def reload

Reloads the attributes of the object as usual and clears marked_for_destruction flag.
def reload
  @marked_for_destruction = false
  @destroyed_by_association = nil
  super
end

def save_belongs_to_association(reflection)

In addition, it will destroy the association if it was marked for destruction.

Saves the associated record if it's new or :autosave is enabled.
def save_belongs_to_association(reflection)
  association = association_instance_get(reflection.name)
  record      = association&.load_target
  if record && !record.destroyed?
    autosave = reflection.options[:autosave]
    if autosave && record.marked_for_destruction?
      record.destroy
    elsif autosave != false
      saved = record.save(validate: !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
      if association.updated?
        self[reflection.foreign_key] = record.id
        association.loaded!
      end
      saved if autosave
    end
  end
end

def save_collection_association(reflection)

ActiveFedora::Base after the AutosaveAssociation module, which it does by default.
This all happens inside a transaction, _if_ the Transactions module is included into

with mark_for_destruction.
In addition, it destroys all children that were marked for destruction

:autosave is enabled on the association.
Saves any new associated records, or all loaded autosave associations if
def save_collection_association(reflection)
  if association = association_instance_get(reflection.name)
    autosave = reflection.options[:autosave]
    if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
      records.each do |record|
        next if record.destroyed?
        saved = true
        if autosave && record.marked_for_destruction?
          association.proxy.destroy(record)
        elsif autosave != false && (@new_record_before_save || record.new_record?)
          if autosave
            saved = association.insert_record(record, false)
          else
            association.insert_record(record)
          end
        elsif autosave
          saved = record.save(validate: false)
        end
        raise ActiveFedora::Rollback unless saved
      end
    end
    # reconstruct the scope now that we know the owner's id
    association.send(:reset_scope) if association.respond_to?(:reset_scope)
  end
end

def validate_collection_association(reflection)

+reflection+.
:autosave is turned on for the association specified by
Validate the associated records if :validate or
def validate_collection_association(reflection)
  if association = association_instance_get(reflection.name)
    if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
      records.each { |record| association_valid?(reflection, record) }
    end
  end
end

def validate_single_association(reflection)

turned on for the association.
Validate the association if :validate or :autosave is
def validate_single_association(reflection)
  association = association_instance_get(reflection.name)
  record      = association&.target
  association_valid?(reflection, record) if record
end