calmchamomile
calmchamomile

Reputation: 13

Freezing HashWithIndifferentAccess in Ruby

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

Answers (2)

user14985849
user14985849

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

Cassandra S.
Cassandra S.

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

Related Questions