module ActiveRecord::AutosaveAssociation
def self.included(base)
def self.included(base) base.class_eval do base.extend(ClassMethods) alias_method_chain :reload, :autosave_associations ASSOCIATION_TYPES.each do |type| base.send("valid_keys_for_#{type}_association") << :autosave end end end
def associated_records_to_validate_or_save(association, new_record, autosave)
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 elsif autosave association.target.select { |record| record.changed_for_autosave? } else association.target.select { |record| record.new_record? } end end
def association_valid?(reflection, association)
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, association) return true if association.destroyed? || association.marked_for_destruction? unless valid = association.valid? if reflection.options[:autosave] association.errors.each_error do |attribute, error| attribute = "#{reflection.name}.#{attribute}" errors.add(attribute, error.dup) unless errors.on(attribute) end else errors.add(reflection.name) end end valid end
def before_save_collection_association
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?
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 mark_for_destruction
This does _not_ actually destroy the record yet, rather it will be destroyed when parent.save is called.
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?
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?
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.each do |reflection| if association = association_instance_get(reflection.name) if [:belongs_to, :has_one].include?(reflection.macro) return true if association.target && association.target.changed_for_autosave? else association.target.each {|record| return true if record.changed_for_autosave? } end end end false end
def reload_with_autosave_associations(options = nil)
def reload_with_autosave_associations(options = nil) @marked_for_destruction = false reload_without_autosave_associations(options) end
def save_belongs_to_association(reflection)
This all happens inside a transaction, _if_ the Transactions module is included into
destruction with mark_for_destruction.
In addition, it will destroy the association if it was marked for
on the association.
Saves the associated record if it's new or :autosave is enabled
def save_belongs_to_association(reflection) if (association = association_instance_get(reflection.name)) && !association.destroyed? autosave = reflection.options[:autosave] if autosave && association.marked_for_destruction? association.destroy elsif autosave != false saved = association.save(!autosave) if association.new_record? || autosave if association.updated? association_id = association.send(reflection.options[:primary_key] || :id) self[reflection.primary_key_name] = association_id # TODO: Removing this code doesn't seem to matter… if reflection.options[:polymorphic] self[reflection.options[:foreign_type]] = association.class.base_class.name.to_s end end saved if autosave end end end
def save_collection_association(reflection)
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? if autosave && record.marked_for_destruction? association.destroy(record) elsif autosave != false && (@new_record_before_save || record.new_record?) if autosave saved = association.send(:insert_record, record, false, false) else association.send(:insert_record, record) end elsif autosave saved = record.save(false) end raise ActiveRecord::Rollback if saved == false end end # reconstruct the SQL queries now that we know the owner's id association.send(:construct_sql) if association.respond_to?(:construct_sql) end end
def save_has_one_association(reflection)
This all happens inside a transaction, _if_ the Transactions module is included into
destruction with mark_for_destruction.
In addition, it will destroy the association if it was marked for
on the association.
Saves the associated record if it's new or :autosave is enabled
def save_has_one_association(reflection) if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed? autosave = reflection.options[:autosave] if autosave && association.marked_for_destruction? association.destroy else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if autosave != false && (new_record? || association.new_record? || association[reflection.primary_key_name] != key || autosave) association[reflection.primary_key_name] = key saved = association.save(!autosave) raise ActiveRecord::Rollback if !saved && autosave saved end end end end
def validate_collection_association(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)
Validate the association if :validate or :autosave is
def validate_single_association(reflection) if (association = association_instance_get(reflection.name)) && !association.target.nil? association_valid?(reflection, association) end end