Don't error when `attributes` is called on a frozen AR model
`freeze` will ultimately end up freezing the `AttributeSet`, which in turn freezes its `@attributes` hash. However, we actually insert a special object to lazily instantiate the values of the hash on demand. When it does need to actually instantiate all of them for iteration (the only case is `ActiveRecord::Base#attributes`, which calls `AttributeSet#to_h`), it will set an instance variable as a performance optimization Since it's just an optimization for subsequent calls, and that method being called at all is a very uncommon case, we can just leave the ivar alone if we're frozen, as opposed to coming up with some overly complicated mechanism for freezing which allows us to continue to modify ourselves. Fixes #17960
This commit is contained in:
parent
4ed60af60d
commit
7daeb98c76
|
@ -76,7 +76,9 @@ module ActiveRecord
|
|||
unless @materialized
|
||||
values.each_key { |key| self[key] }
|
||||
types.each_key { |key| self[key] }
|
||||
@materialized = true
|
||||
unless frozen?
|
||||
@materialized = true
|
||||
end
|
||||
end
|
||||
delegate_hash
|
||||
end
|
||||
|
|
|
@ -178,5 +178,13 @@ module ActiveRecord
|
|||
builder = AttributeSet::Builder.new(foo: Type::Integer.new, bar: Type::Float.new)
|
||||
builder.build_from_database(foo: '1.1')
|
||||
end
|
||||
|
||||
test "freezing doesn't prevent the set from materializing" do
|
||||
builder = AttributeSet::Builder.new(foo: Type::String.new)
|
||||
attributes = builder.build_from_database(foo: "1")
|
||||
|
||||
attributes.freeze
|
||||
assert_equal({ foo: "1" }, attributes.to_hash)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue