Skip to content

Commit 2da863e

Browse files
authored
Merge pull request rails#46739 from adrianna-chang-shopify/ac-hide-callbacks-behaviour-change-behind-flag
Hide changes to `before_committed!` behaviour behind config
2 parents a289f4c + 0b5a705 commit 2da863e

File tree

8 files changed

+67
-7
lines changed

8 files changed

+67
-7
lines changed

activerecord/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Hide changes to before_committed! callback behaviour behind flag.
2+
3+
In #46525, behavior around before_committed! callbacks was changed so that callbacks
4+
would run on every enrolled record in a transaction, not just the first copy of a record.
5+
This change in behavior is now controlled by a configuration option,
6+
`config.active_record.before_committed_on_all_records`. It will be enabled by default on Rails 7.1.
7+
8+
*Adrianna Chang*
9+
110
* The `namespaced_controller` Query Log tag now matches the `controller` format
211

312
For example, a request processed by `NameSpaced::UsersController` will now log as:

activerecord/lib/active_record.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ def self.global_executor_concurrency # :nodoc:
274274
singleton_class.attr_accessor :belongs_to_required_validates_foreign_key
275275
self.belongs_to_required_validates_foreign_key = true
276276

277+
singleton_class.attr_accessor :before_committed_on_all_records
278+
self.before_committed_on_all_records = false
279+
277280
##
278281
# :singleton-method:
279282
# Specify a threshold for the size of query result sets. If the number of

activerecord/lib/active_record/connection_adapters/abstract/transaction.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -171,14 +171,18 @@ def before_commit_records
171171
return unless records
172172

173173
if @run_commit_callbacks
174-
ite = unique_records
174+
if ActiveRecord.before_committed_on_all_records
175+
ite = unique_records
175176

176-
instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
177-
candidates[record] = record
178-
end
177+
instances_to_run_callbacks_on = records.each_with_object({}) do |record, candidates|
178+
candidates[record] = record
179+
end
179180

180-
run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
181-
record.before_committed! if should_run_callbacks
181+
run_action_on_records(ite, instances_to_run_callbacks_on) do |record, should_run_callbacks|
182+
record.before_committed! if should_run_callbacks
183+
end
184+
else
185+
records.uniq.each(&:before_committed!)
182186
end
183187
end
184188
end

activerecord/test/cases/touch_later_test.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,31 @@ def test_touching_three_deep
123123
assert_not_equal trees(:root).reload.updated_at, previous_tree_updated_at
124124
end
125125

126-
def test_touching_through_nested_attributes
126+
def test_touching_through_nested_attributes_without_before_committed_on_all_records
127+
original = ActiveRecord.before_committed_on_all_records
128+
ActiveRecord.before_committed_on_all_records = false
129+
130+
time = Time.now.utc - 25.days
131+
132+
owner = owners(:blackbeard)
133+
134+
owner.touch(time: time)
135+
136+
assert_equal time.to_i, owner.reload.updated_at.to_i
137+
138+
owner.update pets_attributes: { "0" => { id: "1", name: "Alfred" } }
139+
140+
# The second copy of the record is not touched, so the owner's updated_at
141+
# remains the same.
142+
assert_equal time.to_i, owner.reload.updated_at.to_i
143+
ensure
144+
ActiveRecord.before_committed_on_all_records = original
145+
end
146+
147+
def test_touching_through_nested_attributes_with_before_committed_on_all_records
148+
original = ActiveRecord.before_committed_on_all_records
149+
ActiveRecord.before_committed_on_all_records = true
150+
127151
time = Time.now.utc - 25.days
128152

129153
owner = owners(:blackbeard)
@@ -135,5 +159,7 @@ def test_touching_through_nested_attributes
135159
owner.update pets_attributes: { "0" => { id: "1", name: "Alfred" } }
136160

137161
assert_not_equal time.to_i, owner.reload.updated_at.to_i
162+
ensure
163+
ActiveRecord.before_committed_on_all_records = original
138164
end
139165
end

activerecord/test/storage/test.sqlite3

Whitespace-only changes.

guides/source/configuring.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Below are the default values associated with each target version. In cases of co
6464
- [`config.action_dispatch.default_headers`](#config-action-dispatch-default-headers): `{ "X-Frame-Options" => "SAMEORIGIN", "X-XSS-Protection" => "0", "X-Content-Type-Options" => "nosniff", "X-Permitted-Cross-Domain-Policies" => "none", "Referrer-Policy" => "strict-origin-when-cross-origin" }`
6565
- [`config.active_job.use_big_decimal_serializer`](#config-active-job-use-big-decimal-serializer): `true`
6666
- [`config.active_record.allow_deprecated_singular_associations_name`](#config-active-record-allow-deprecated-singular-associations-name): `false`
67+
- [`config.active_record.before_committed_on_all_records`](#config-active-record-before-committed-on-all-records): `true`
6768
- [`config.active_record.belongs_to_required_validates_foreign_key`](#config-active-record-belongs-to-required-validates-foreign-key): `false`
6869
- [`config.active_record.query_log_tags_format`](#config-active-record-query-log-tags-format): `:sqlcommenter`
6970
- [`config.active_record.raise_on_assign_to_attr_readonly`](#config-active-record-raise-on-assign-to-attr-readonly): `true`
@@ -1006,6 +1007,17 @@ The options are `:schema_search_path` (the default) which dumps any schemas list
10061007
`:all` which always dumps all schemas regardless of the `schema_search_path`,
10071008
or a string of comma separated schemas.
10081009

1010+
#### `config.active_record.before_committed_on_all_records`
1011+
1012+
Enable before_committed! callbacks on all enrolled records in a transaction.
1013+
The previous behavior was to only run the callbacks on the first copy of a record
1014+
if there were multiple copies of the same record enrolled in the transaction.
1015+
1016+
| Starting with version | The default value is |
1017+
| --------------------- | -------------------- |
1018+
| (original) | `false` |
1019+
| 7.1 | `true` |
1020+
10091021
#### `config.active_record.belongs_to_required_by_default`
10101022

10111023
Is a boolean value and controls whether a record fails validation if

railties/lib/rails/application/configuration.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def load_defaults(target_version)
291291
active_record.query_log_tags_format = :sqlcommenter
292292
active_record.raise_on_assign_to_attr_readonly = true
293293
active_record.belongs_to_required_validates_foreign_key = false
294+
active_record.before_committed_on_all_records = true
294295
end
295296

296297
if respond_to?(:action_dispatch)

railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_1.rb.tt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,8 @@
121121
# Enable precompilation of `config.filter_parameters`. Precompilation can
122122
# improve filtering performance, depending on the quantity and types of filters.
123123
# Rails.application.config.precompile_filter_parameters = true
124+
125+
# Enable before_committed! callbacks on all enrolled records in a transaction.
126+
# The previous behavior was to only run the callbacks on the first copy of a record
127+
# if there were multiple copies of the same record enrolled in the transaction.
128+
# Rails.application.config.active_record.before_committed_on_all_records = true

0 commit comments

Comments
 (0)