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:
Sean Griffin 2014-12-08 12:45:21 -07:00
parent 4ed60af60d
commit 7daeb98c76
2 changed files with 11 additions and 1 deletions

View File

@ -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

View File

@ -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