@@ -156,6 +156,25 @@ def self.valid_options
156156
157157 module ClassMethods # :nodoc:
158158 private
159+ def define_non_cyclic_method ( name , &block )
160+ return if method_defined? ( name , false )
161+
162+ define_method ( name ) do |*args |
163+ result = true ; @_already_called ||= { }
164+ # Loop prevention for validation of associations
165+ unless @_already_called [ name ]
166+ begin
167+ @_already_called [ name ] = true
168+ result = instance_eval ( &block )
169+ ensure
170+ @_already_called [ name ] = false
171+ end
172+ end
173+
174+ result
175+ end
176+ end
177+
159178 # Adds validation and save callbacks for the association as specified by
160179 # the +reflection+.
161180 #
@@ -173,12 +192,12 @@ def add_autosave_association_callbacks(reflection)
173192 if reflection . collection?
174193 around_save :around_save_collection_association
175194
176- define_method ( save_method ) { save_collection_association ( reflection ) }
195+ define_non_cyclic_method ( save_method ) { save_collection_association ( reflection ) }
177196 # Doesn't use after_save as that would save associations added in after_create/after_update twice
178197 after_create save_method
179198 after_update save_method
180199 elsif reflection . has_one?
181- define_method ( save_method ) { save_has_one_association ( reflection ) }
200+ define_non_cyclic_method ( save_method ) { save_has_one_association ( reflection ) }
182201 # Configures two callbacks instead of a single after_save so that
183202 # the model may rely on their execution order relative to its
184203 # own callbacks.
@@ -190,7 +209,7 @@ def add_autosave_association_callbacks(reflection)
190209 after_create save_method
191210 after_update save_method
192211 else
193- define_method ( save_method ) { throw ( :abort ) if save_belongs_to_association ( reflection ) == false }
212+ define_non_cyclic_method ( save_method ) { throw ( :abort ) if save_belongs_to_association ( reflection ) == false }
194213 before_save save_method
195214 end
196215
@@ -208,7 +227,7 @@ def define_autosave_validation_callbacks(reflection)
208227 method = :validate_belongs_to_association
209228 end
210229
211- define_method ( validation_method ) { send ( method , reflection ) }
230+ define_non_cyclic_method ( validation_method ) { send ( method , reflection ) }
212231 validate validation_method
213232 after_validation :_ensure_no_duplicate_errors
214233 end
@@ -264,6 +283,7 @@ def changed_for_autosave?(memory)
264283 end
265284 end
266285
286+ #TODO: can go I think
267287 def validating_belongs_to_for? ( association )
268288 @validating_belongs_to_for ||= { }
269289 @validating_belongs_to_for [ association ]
@@ -323,7 +343,7 @@ def validate_has_one_association(reflection)
323343 return if inverse_association && ( record . validating_belongs_to_for? ( inverse_association ) ||
324344 record . autosaving_belongs_to_for? ( inverse_association ) )
325345
326- association_valid? ( reflection , record , @memory )
346+ association_valid? ( association , record , @memory )
327347 end
328348
329349 # Validate the association if <tt>:validate</tt> or <tt>:autosave</tt> is
@@ -356,48 +376,19 @@ def validate_collection_association(reflection)
356376 # Returns whether or not the association is valid and applies any errors to
357377 # the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
358378 # enabled records if they're marked_for_destruction? or destroyed.
359- def association_valid? ( reflection , record , memory )
379+ def association_valid? ( association , record , memory )
360380 return true if record . destroyed? || ( association . options [ :autosave ] && record . marked_for_destruction? )
361381
362382 context = validation_context if custom_validation_context?
363383
364- if memory . has_key? ( "valid#{ record . object_id } " )
365- return memory [ "valid#{ record . object_id } " ]
366- else
367- memory [ "valid#{ record . object_id } " ] = true
368-
369- valid = record . valid? ( context , memory )
370- if !valid
371- if record . changed? || record . new_record? || context
372- associated_errors = record . errors . objects
373- else
374- # If there are existing invalid records in the DB, we should still be able to reference them.
375- # Unless a record (no matter where in the association chain) is invalid and is being changed.
376- associated_errors = record . errors . objects . select { |error | error . is_a? ( Associations ::NestedError ) }
377- end
384+ return memory [ "valid#{ record . object_id } " ] if memory . has_key? ( "valid#{ record . object_id } " )
378385
379- if association . options [ :autosave ]
380- return if equal? ( record )
381-
382- associated_errors . each { |error |
383- errors . objects . append (
384- Associations ::NestedError . new ( association , error )
385- )
386- }
387- elsif associated_errors . any?
388- errors . add ( association . reflection . name )
389- end
390-
391- valid = errors . any?
392- end
393- memory [ "valid#{ record . object_id } " ] = valid
394- valid
395- end
396- end
386+ memory [ "valid#{ record . object_id } " ] = true
387+
388+ return true if record . valid? ( context , memory )
397389
398- def normalize_reflection_attribute ( indexed_attribute , reflection , index , attribute )
399- if indexed_attribute
400- "#{ reflection . name } [#{ index } ].#{ attribute } "
390+ if record . changed? || record . new_record? || context
391+ associated_errors = record . errors . objects
401392 else
402393 # If there are existing invalid records in the DB, we should still be able to reference them.
403394 # Unless a record (no matter where in the association chain) is invalid and is being changed.
@@ -416,7 +407,7 @@ def normalize_reflection_attribute(indexed_attribute, reflection, index, attribu
416407 errors . add ( association . reflection . name )
417408 end
418409
419- errors . any?
410+ memory [ "valid #{ record . object_id } " ] = errors . any?
420411 end
421412
422413 # Is used as an around_save callback to check while saving a collection
@@ -531,7 +522,7 @@ def save_has_one_association(reflection)
531522 saved = if @memory . has_key? ( "saved#{ record . object_id } " )
532523 @memory [ "saved#{ record . object_id } " ]
533524 else
534- record . save ( validate : !autosave , memory : @memory )
525+ @memory [ "saved #{ record . object_id } " ] = record . save ( validate : !autosave , memory : @memory )
535526 end
536527 raise ActiveRecord ::Rollback if !saved && autosave
537528 saved
@@ -584,7 +575,7 @@ def save_belongs_to_association(reflection)
584575 begin
585576 @autosaving_belongs_to_for ||= { }
586577 @autosaving_belongs_to_for [ association ] = true
587- record . save ( validate : !autosave , memory : @memory )
578+ @memory [ "saved #{ record . object_id } " ] = record . save ( validate : !autosave , memory : @memory )
588579 ensure
589580 @autosaving_belongs_to_for [ association ] = false
590581 end
0 commit comments