Don't call after_commit callbacks despite a record isn't saved

Regardless of a record isn't saved (e.g. validation is failed),
`after_commit` / `after_rollback` callbacks are invoked for now.

To fix the issue, this adds a record to the current transaction only
when a record is actually saved.

Fixes #29747.
Closes #29833.
This commit is contained in:
Ryuta Kamizono 2019-04-10 17:49:42 +09:00
parent faf07d1468
commit 9252da9659
4 changed files with 43 additions and 5 deletions

View File

@ -1,3 +1,9 @@
* Don't call commit/rollback callbacks despite a record isn't saved.
Fixes #29747.
*Ryuta Kamizono*
* Fix circular `autosave: true` causes invalid records to be saved.
Prior to the fix, when there was a circular series of `autosave: true`

View File

@ -376,9 +376,19 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
add_to_transaction
unless has_transactional_callbacks?
sync_with_transaction_state
set_transaction_state(self.class.connection.transaction_state)
end
remember_transaction_record_state
status = yield
raise ActiveRecord::Rollback unless status
ensure
if has_transactional_callbacks? &&
(@_new_record_before_last_commit && !new_record? || _trigger_update_callback || _trigger_destroy_callback)
self.class.connection.add_transaction_record(self)
end
end
status
end

View File

@ -458,10 +458,6 @@ class CallbacksTest < ActiveRecord::TestCase
[ :before_validation, :object ],
[ :before_validation, :block ],
[ :before_validation, :throwing_abort ],
[ :after_rollback, :block ],
[ :after_rollback, :object ],
[ :after_rollback, :proc ],
[ :after_rollback, :method ],
], david.history
end

View File

@ -111,6 +111,32 @@ class TransactionCallbacksTest < ActiveRecord::TestCase
assert_equal [:after_commit], @first.history
end
def test_dont_call_any_callbacks_after_transaction_commits_for_invalid_record
@first.after_commit_block { |r| r.history << :after_commit }
@first.after_rollback_block { |r| r.history << :after_rollback }
def @first.valid?(*)
false
end
assert_not @first.save
assert_equal [], @first.history
end
def test_dont_call_any_callbacks_after_explicit_transaction_commits_for_invalid_record
@first.after_commit_block { |r| r.history << :after_commit }
@first.after_rollback_block { |r| r.history << :after_rollback }
def @first.valid?(*)
false
end
@first.transaction do
assert_not @first.save
end
assert_equal [], @first.history
end
def test_only_call_after_commit_on_save_after_transaction_commits_for_saving_record
record = TopicWithCallbacks.new(title: "New topic", written_on: Date.today)
record.after_commit_block(:save) { |r| r.history << :after_save }