aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/autosave_association.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/autosave_association.rb')
-rw-r--r--activerecord/lib/active_record/autosave_association.rb107
1 files changed, 68 insertions, 39 deletions
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index c3dda29d03..748cc99a62 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -116,28 +116,44 @@ module ActiveRecord
module AutosaveAssociation
extend ActiveSupport::Concern
- ASSOCIATION_TYPES = %w{ has_one belongs_to has_many has_and_belongs_to_many }
+ ASSOCIATION_TYPES = %w{ HasOne HasMany BelongsTo HasAndBelongsToMany }
+
+ module AssociationBuilderExtension #:nodoc:
+ def self.included(base)
+ base.valid_options << :autosave
+ end
+
+ def build
+ reflection = super
+ model.send(:add_autosave_association_callbacks, reflection)
+ reflection
+ end
+ end
included do
ASSOCIATION_TYPES.each do |type|
- send("valid_keys_for_#{type}_association") << :autosave
+ Associations::Builder.const_get(type).send(:include, AssociationBuilderExtension)
end
end
module ClassMethods
private
- # def belongs_to(name, options = {})
- # super
- # add_autosave_association_callbacks(reflect_on_association(name))
- # end
- ASSOCIATION_TYPES.each do |type|
- module_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{type}(name, options = {})
- super
- add_autosave_association_callbacks(reflect_on_association(name))
+ def define_non_cyclic_method(name, reflection, &block)
+ define_method(name) do |*args|
+ result = true; @_already_called ||= {}
+ # Loop prevention for validation of associations
+ unless @_already_called[[name, reflection.name]]
+ begin
+ @_already_called[[name, reflection.name]]=true
+ result = instance_eval(&block)
+ ensure
+ @_already_called[[name, reflection.name]]=false
+ end
end
- CODE
+
+ result
+ end
end
# Adds validation and save callbacks for the association as specified by
@@ -160,7 +176,7 @@ module ActiveRecord
if collection
before_save :before_save_collection_association
- define_method(save_method) { save_collection_association(reflection) }
+ define_non_cyclic_method(save_method, reflection) { save_collection_association(reflection) }
# Doesn't use after_save as that would save associations added in after_create/after_update twice
after_create save_method
after_update save_method
@@ -178,7 +194,7 @@ module ActiveRecord
after_create save_method
after_update save_method
else
- define_method(save_method) { save_belongs_to_association(reflection) }
+ define_non_cyclic_method(save_method, reflection) { save_belongs_to_association(reflection) }
before_save save_method
end
end
@@ -186,7 +202,7 @@ module ActiveRecord
if reflection.validate? && !method_defined?(validation_method)
method = (collection ? :validate_collection_association : :validate_single_association)
- define_method(validation_method) { send(method, reflection) }
+ define_non_cyclic_method(validation_method, reflection) { send(method, reflection) }
validate validation_method
end
end
@@ -227,7 +243,7 @@ module ActiveRecord
# unless the parent is/was a new record itself.
def associated_records_to_validate_or_save(association, new_record, autosave)
if new_record
- association
+ association && association.target
elsif autosave
association.target.find_all { |record| record.changed_for_autosave? }
else
@@ -247,9 +263,9 @@ module ActiveRecord
# Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
# turned on for the association.
def validate_single_association(reflection)
- if (association = association_instance_get(reflection.name)) && !association.target.nil?
- association_valid?(reflection, association)
- end
+ association = association_instance_get(reflection.name)
+ record = association && association.target
+ association_valid?(reflection, record) if record
end
# Validate the associated records if <tt>:validate</tt> or
@@ -266,12 +282,12 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, association)
- return true if association.destroyed? || association.marked_for_destruction?
+ def association_valid?(reflection, record)
+ return true if record.destroyed? || record.marked_for_destruction?
- unless valid = association.valid?
+ unless valid = record.valid?
if reflection.options[:autosave]
- association.errors.each do |attribute, message|
+ record.errors.each do |attribute, message|
attribute = "#{reflection.name}.#{attribute}"
errors[attribute] << message
errors[attribute].uniq!
@@ -303,23 +319,31 @@ module ActiveRecord
autosave = reflection.options[:autosave]
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
+ begin
records.each do |record|
next if record.destroyed?
+ saved = true
+
if autosave && record.marked_for_destruction?
- association.destroy(record)
+ association.proxy.destroy(record)
elsif autosave != false && (@new_record_before_save || record.new_record?)
if autosave
- saved = association.send(:insert_record, record, false, false)
+ saved = association.insert_record(record, false)
else
- association.send(:insert_record, record)
+ association.insert_record(record)
end
elsif autosave
saved = record.save(:validate => false)
end
- raise ActiveRecord::Rollback if saved == false
+ raise ActiveRecord::Rollback unless saved
+ end
+ rescue
+ records.each {|x| IdentityMap.remove(x) } if IdentityMap.enabled?
+ raise
end
+
end
# reconstruct the scope now that we know the owner's id
@@ -336,16 +360,18 @@ module ActiveRecord
# This all happens inside a transaction, _if_ the Transactions module is included into
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
def save_has_one_association(reflection)
- if (association = association_instance_get(reflection.name)) && !association.target.nil? && !association.destroyed?
+ association = association_instance_get(reflection.name)
+ record = association && association.load_target
+ if record && !record.destroyed?
autosave = reflection.options[:autosave]
- if autosave && association.marked_for_destruction?
- association.destroy
+ if autosave && record.marked_for_destruction?
+ record.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(:validate => !autosave)
+ if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave)
+ record[reflection.foreign_key] = key
+ saved = record.save(:validate => !autosave)
raise ActiveRecord::Rollback if !saved && autosave
saved
end
@@ -357,17 +383,20 @@ module ActiveRecord
#
# In addition, it will destroy the association if it was marked for destruction.
def save_belongs_to_association(reflection)
- if (association = association_instance_get(reflection.name)) && !association.destroyed?
+ association = association_instance_get(reflection.name)
+ record = association && association.load_target
+ if record && !record.destroyed?
autosave = reflection.options[:autosave]
- if autosave && association.marked_for_destruction?
- association.destroy
+ if autosave && record.marked_for_destruction?
+ record.destroy
elsif autosave != false
- saved = association.save(:validate => !autosave) if association.new_record? || autosave
+ saved = record.save(:validate => !autosave) if record.new_record? || (autosave && record.changed_for_autosave?)
if association.updated?
- association_id = association.send(reflection.options[:primary_key] || :id)
- self[reflection.primary_key_name] = association_id
+ association_id = record.send(reflection.options[:primary_key] || :id)
+ self[reflection.foreign_key] = association_id
+ association.loaded!
end
saved if autosave