Reputation: 13
I'm trying to freeze a nested HashWithIndifferentAccess in Ruby and came across some unexpected behavior. Without wrapping neither the inner or outer hash with .with_indifferent_access
, it works as expected:
v = { 'a' => { 'b' => 2 }.freeze }.freeze
v['a'].frozen? => true
Both v_1[:a]
and v_2[:a]
are HashWithIndifferentAccess's, but only v_1[:a] is frozen. Why is that?
v_1 = { 'a' => { 'b' => 2 }.with_indifferent_access.freeze }.with_indifferent_access.freeze
v_1[:a].frozen? => true
v_2 = { 'a' => { 'b' => 2 }.freeze }.with_indifferent_access.freeze
v_2[:a].frozen? => false
Thanks in advance!
Upvotes: 1
Views: 725
Reputation: 26
So we're looking at how the nested hash behaves differently between
v_1 = { a: { b: 2 } }.with_indifferent_access
v_2 = { a: { b: 2 }.with_indifferent_access }.with_indifferent_access
When you call Hash#with_indifferent_access, it creates a new ActiveSupport::HashWithIndifferentAccess
object; and then calls #update
to insert all of the key/value pairs from the original hash into the new object (ActiveSupport::HashWithIndifferentAccess#update), which calls #convert_values
with the nested hash:
def convert_value(value, options = {})
if value.is_a? Hash
if options[:for] == :to_hash
value.to_hash
else
value.nested_under_indifferent_access
end
...
So both { b: 2 }
and { b: 2 }.with_indifferent_access
will have #nested_under_indifferent_access
called on them. But that’s a different method for Hash than it is for HashWithIndifferentAccess. In the core_ext file, Hash#nested_under_indifferent_access
calls HashWithIndifferentAccess.new(self)
, but HashWithIndifferentAccess#nested_under_indifferent_access
just returns self
.
So {'b' => 2}.nested_under_indifferent_access
returns a new object, but {'b' => 2}.with_indifferent_access.nested_under_indifferent_access
doesn’t do anything to the object. Which is why if the first one is frozen, you get back a different (unfrozen by default) object, and if the second one is frozen, it stays frozen.
Upvotes: 1
Reputation: 770
If you look at the implementation details for with_indifferent_access
you can see that it is actually a dup
of the original hash. dup
-ed hashes are not frozen. By calling freeze
on the returned hash (from with_indifferent_access
), you freeze it.
Upvotes: 1