Skip to content

Commit a518b5a

Browse files
committed
[Fix rails#50897] Autosaving has_one sets foreign key attribute when unchanged
1 parent 3528b9d commit a518b5a

File tree

4 files changed

+38
-7
lines changed

4 files changed

+38
-7
lines changed

activerecord/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
* Fix `has_one` association autosave setting the foreign key attribute when it is unchanged.
2+
3+
This behaviour is also inconsistent with autosaving `belongs_to` and can have unintended side effects like raising
4+
an `ActiveRecord::ReadOnlyAttributeError` when the foreign key attribute is marked as read-only.
5+
6+
*Joshua Young*
7+
18
* Remove deprecated behavior that would rollback a transaction block when exited using `return`, `break` or `throw`.
29

310
*Rafael Mendonça França*

activerecord/lib/active_record/autosave_association.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,8 @@ def save_has_one_association(reflection)
458458
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
459459

460460
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
461-
record[foreign_key] = _read_attribute(primary_key)
461+
association_id = _read_attribute(primary_key)
462+
record[foreign_key] = association_id unless record[foreign_key] == association_id
462463
end
463464
association.set_inverse_instance(record)
464465
end

activerecord/test/cases/autosave_association_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,13 @@ def test_callbacks_firing_order_on_save
302302
eye.update(iris_attributes: { color: "blue" })
303303
assert_equal [false, false, false, false], eye.after_save_callbacks_stack
304304
end
305+
306+
def test_foreign_key_attribute_is_not_set_unless_changed
307+
eye = Eye.create!(iris_with_read_only_foreign_key_attributes: { color: "honey" })
308+
assert_nothing_raised do
309+
eye.update!(override_iris_with_read_only_foreign_key_color: true)
310+
end
311+
end
305312
end
306313

307314
class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase

activerecord/test/models/eye.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@ class Eye < ActiveRecord::Base
44
attr_reader :after_create_callbacks_stack
55
attr_reader :after_update_callbacks_stack
66
attr_reader :after_save_callbacks_stack
7+
attr_writer :override_iris_with_read_only_foreign_key_color
78

89
# Callbacks configured before the ones has_one sets up.
9-
after_create :trace_after_create
10-
after_update :trace_after_update
11-
after_save :trace_after_save
10+
after_create :trace_after_create, if: :iris
11+
after_update :trace_after_update, if: :iris
12+
after_save :trace_after_save, if: :iris
1213

1314
has_one :iris
1415
accepts_nested_attributes_for :iris
1516

1617
# Callbacks configured after the ones has_one sets up.
17-
after_create :trace_after_create2
18-
after_update :trace_after_update2
19-
after_save :trace_after_save2
18+
after_create :trace_after_create2, if: :iris
19+
after_update :trace_after_update2, if: :iris
20+
after_save :trace_after_save2, if: :iris
2021

2122
def trace_after_create
2223
(@after_create_callbacks_stack ||= []) << !iris.persisted?
@@ -32,8 +33,23 @@ def trace_after_save
3233
(@after_save_callbacks_stack ||= []) << iris.has_changes_to_save?
3334
end
3435
alias trace_after_save2 trace_after_save
36+
37+
has_one :iris_with_read_only_foreign_key, class_name: "IrisWithReadOnlyForeignKey", foreign_key: :eye_id
38+
accepts_nested_attributes_for :iris_with_read_only_foreign_key
39+
40+
before_save :set_iris_with_read_only_foreign_key_color_to_blue, if: -> {
41+
iris_with_read_only_foreign_key && @override_iris_with_read_only_foreign_key_color
42+
}
43+
44+
def set_iris_with_read_only_foreign_key_color_to_blue
45+
iris_with_read_only_foreign_key.color = "blue"
46+
end
3547
end
3648

3749
class Iris < ActiveRecord::Base
3850
belongs_to :eye
3951
end
52+
53+
class IrisWithReadOnlyForeignKey < Iris
54+
attr_readonly :eye_id
55+
end

0 commit comments

Comments
 (0)