Only assign @new_record_before_save once in autosave_association

If we defined a callback before an association that updates the object, then this may end up being
manipulated to being `false` when it should be `true`. We guard this be only defining it once.

The implication of it being false, in this case, is that the children aren't updated with the parent_id
and so they fail to associate to one another.

See https://github.com/rails/rails/issues/38120 for more details
This commit is contained in:
Julian Nadeau 2020-01-06 12:51:56 -05:00
parent 80d98450c3
commit 901d62c586
No known key found for this signature in database
GPG Key ID: CF3518122191D555
3 changed files with 32 additions and 1 deletions

View File

@ -365,7 +365,12 @@ module ActiveRecord
# Is used as a before_save callback to check while saving a collection
# association whether or not the parent was a new record before saving.
def before_save_collection_association
@new_record_before_save = new_record?
# If we defined a callback that updates the object before we defined the association, then this ivar may end up being
# manipulated to being `false` when it should be `true`. We guard this be only defining it once.
# See https://github.com/rails/rails/issues/38120 for more details
unless defined?(@new_record_before_save)
@new_record_before_save = new_record?
end
end
def after_save_collection_association

View File

@ -35,6 +35,30 @@ require "models/tuning_peg"
require "models/reply"
class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase
def test_autosave_works_even_when_other_callbacks_update_the_parent_model
reference = Class.new(ActiveRecord::Base) do
self.table_name = "references"
def self.name; "Reference"; end
end
person = Class.new(ActiveRecord::Base) do
self.table_name = "people"
def self.name; "Person"; end
# It is necessary that the after_create is before the has_many _and_ that it updates the model.
# This replicates a bug found in https://github.com/rails/rails/issues/38120
after_create { update(first_name: "first name") }
has_many :references, autosave: true, anonymous_class: reference
end
reference_instance = reference.create!
person_instance = person.create!(first_name: "foo", references: [reference_instance])
reference_instance.reload
assert_equal person_instance.id, reference_instance.person_id
assert_equal "first name", person_instance.first_name # Make sure the after_create is actually called
end
def test_autosave_does_not_pass_through_non_custom_validation_contexts
person = Class.new(ActiveRecord::Base) {
self.table_name = "people"

View File

@ -117,6 +117,8 @@ Here is a list with all the available Active Record callbacks, listed in the sam
WARNING. `after_save` runs both on create and update, but always _after_ the more specific callbacks `after_create` and `after_update`, no matter the order in which the macro calls were executed.
WARNING. Care should be taken in callbacks to avoid updating attributes. For example, avoid running `update(attribute: "value")` and similar code during callbacks. This can alter the state of the model and may result in unexpected side effects during commit. Instead, you should try to assign values in the `before_create` or earlier callbacks.
NOTE: `before_destroy` callbacks should be placed before `dependent: :destroy`
associations (or use the `prepend: true` option), to ensure they execute before
the records are deleted by `dependent: :destroy`.