Skip to content

Commit a8978b2

Browse files
authored
Merge pull request rails#46602 from hmcguire-shopify/fix-lazy-readonly-writer
Fix undefined attribute method with attr_readonly
2 parents cfa5778 + 429f0a7 commit a8978b2

File tree

4 files changed

+31
-22
lines changed

4 files changed

+31
-22
lines changed

activerecord/lib/active_record/associations/collection_association.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def merge_target_lists(persisted, memory)
324324
persisted.map! do |record|
325325
if mem_record = memory.delete(record)
326326

327-
((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save).each do |name|
327+
((record.attribute_names & mem_record.attribute_names) - mem_record.changed_attribute_names_to_save - mem_record.class._attr_readonly).each do |name|
328328
mem_record._write_attribute(name, record[name])
329329
end
330330

activerecord/lib/active_record/readonly_attributes.rb

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,11 @@ module ClassMethods
2828
# post.title = "a different title" # raises ActiveRecord::ReadonlyAttributeError
2929
# post.update(title: "a different title") # raises ActiveRecord::ReadonlyAttributeError
3030
def attr_readonly(*attributes)
31-
new_attributes = attributes.map(&:to_s).reject { |a| _attr_readonly.include?(a) }
31+
self._attr_readonly |= attributes.map(&:to_s)
3232

3333
if ActiveRecord.raise_on_assign_to_attr_readonly
34-
new_attributes.each do |attribute|
35-
define_method("#{attribute}=") do |value|
36-
raise ReadonlyAttributeError.new(attribute) unless new_record?
37-
38-
super(value)
39-
end
40-
end
41-
4234
include(HasReadonlyAttributes)
4335
end
44-
45-
self._attr_readonly = Set.new(new_attributes) + _attr_readonly
4636
end
4737

4838
# Returns an array of all the attributes that have been specified as readonly.
@@ -56,12 +46,14 @@ def readonly_attribute?(name) # :nodoc:
5646
end
5747

5848
module HasReadonlyAttributes # :nodoc:
59-
def write_attribute(attr_name, value)
60-
if !new_record? && self.class.readonly_attribute?(attr_name.to_s)
61-
raise ReadonlyAttributeError.new(attr_name)
62-
end
49+
[:write_attribute, :_write_attribute].each do |name|
50+
define_method(name) do |attr_name, value|
51+
if !new_record? && self.class.readonly_attribute?(attr_name.to_s)
52+
raise ReadonlyAttributeError.new(attr_name)
53+
end
6354

64-
super
55+
super(attr_name, value)
56+
end
6557
end
6658
end
6759
end

activerecord/test/cases/base_test.rb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ class ReadonlyTitlePost < Post
6666
attr_readonly :title
6767
end
6868

69+
class ReadonlyTitleAbstractPost < ActiveRecord::Base
70+
self.abstract_class = true
71+
72+
attr_readonly :title
73+
end
74+
75+
class ReadonlyTitlePostWithAbstractParent < ReadonlyTitleAbstractPost
76+
self.table_name = "posts"
77+
end
78+
6979
previous_value, ActiveRecord.raise_on_assign_to_attr_readonly = ActiveRecord.raise_on_assign_to_attr_readonly, false
7080

7181
class NonRaisingPost < Post
@@ -699,7 +709,7 @@ def test_comparison_with_different_objects_in_array
699709
end
700710

701711
def test_readonly_attributes
702-
assert_equal Set.new([ "title" ]), ReadonlyTitlePost.readonly_attributes
712+
assert_equal [ "title" ], ReadonlyTitlePost.readonly_attributes
703713

704714
post = ReadonlyTitlePost.create(title: "cannot change this", body: "changeable")
705715
assert_equal "cannot change this", post.title
@@ -750,7 +760,7 @@ def test_readonly_attributes
750760
end
751761

752762
def test_readonly_attributes_on_a_new_record
753-
assert_equal Set.new([ "title" ]), ReadonlyTitlePost.readonly_attributes
763+
assert_equal [ "title" ], ReadonlyTitlePost.readonly_attributes
754764

755765
post = ReadonlyTitlePost.new(title: "can change this until you save", body: "changeable")
756766
assert_equal "can change this until you save", post.title
@@ -766,7 +776,6 @@ def test_readonly_attributes_on_a_new_record
766776
assert_equal "changed via write_attribute", post.title
767777
assert_equal "changed via write_attribute", post.body
768778

769-
770779
post.assign_attributes(body: "changed via assign_attributes", title: "changed via assign_attributes")
771780
assert_equal "changed via assign_attributes", post.title
772781
assert_equal "changed via assign_attributes", post.body
@@ -783,8 +792,16 @@ def test_readonly_attributes_on_a_new_record
783792
assert_equal "changed via []=", post.body
784793
end
785794

795+
def test_readonly_attributes_in_abstract_class_descendant
796+
assert_equal [ "title" ], ReadonlyTitlePostWithAbstractParent.readonly_attributes
797+
798+
assert_nothing_raised do
799+
ReadonlyTitlePostWithAbstractParent.new(title: "can change this until you save")
800+
end
801+
end
802+
786803
def test_readonly_attributes_when_configured_to_not_raise
787-
assert_equal Set.new([ "title" ]), NonRaisingPost.readonly_attributes
804+
assert_equal [ "title" ], NonRaisingPost.readonly_attributes
788805

789806
post = NonRaisingPost.create(title: "cannot change this", body: "changeable")
790807
assert_equal "cannot change this", post.title

activerecord/test/cases/locking_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def test_lock_with_custom_column_without_default_queries_count
455455
end
456456

457457
def test_readonly_attributes
458-
assert_equal Set.new([ "name" ]), ReadonlyNameShip.readonly_attributes
458+
assert_equal [ "name" ], ReadonlyNameShip.readonly_attributes
459459

460460
s = ReadonlyNameShip.create(name: "unchangeable name")
461461
s.reload

0 commit comments

Comments
 (0)