Bug in HashWithIndifferentAccess?

Hi all,

I think I have stumbled upon a bug in HashWithIndifferentAccess.

I cannot pinpoint it exactly, but I have written a small test case that exhibits the buggy behavior:


The same code works fine if we use a regular Hash for @store instead of a HashWithIndifferentAccess.

Is this a known issue or should I fill a bug report?

Thank you,

You are not storing waldo where you think you are storing it. On first access @store[:foo] is already initialized, so it does away with your waldo assignment, then on second and third access you are populating it. my_hash[:baz] is @store[:foo][:baz].


I don’t believe that’s correct. And testing it out by replacing the HashWithIndifferentAccess with a regular Ruby Hash gives the expected result, so it’s definitely different behavior than expected.

That being said, I just tested it on Rails 4.2.3 and it works as expected, so I’m not sure why Yves is seeing a different result.

This code in your example:

  @store[:foo] ||= {bar: 'BAR'}

does not do what you may be thinking it does. HWIA overrides the `=` operator:


to convert incoming plain Hash objects into HWIA. So the object that eventually is stored in `@store[:foo]` is NOT the one that was passed to the assignment operator.

You can check this explicitly:

  require 'active_support/all'

  @store = ActiveSupport::HashWithIndifferentAccess.new

  def my_hash     @store[:foo] ||= {bar: 'BAR'}   end

  first_time = my_hash # => {:bar =>”BAR”}   second_time = my_hash # => {“bar”=>”BAR”}

Note that `first_time` and `second_time` don’t have matching `inspect` results.

Assignment operators *always* return the value passed on the right-hand side, regardless of what the underlying `=` method returns. This means that in cases where the object doesn’t require conversion (coalwater’s modification from your Gist) the object returned from `||=` DOES match, and Waldo is found.

Not sure if there’s a fix for this - the converting-on-= behavior is something many applications are likely to depend on, and the behavior of assignment operators is a core Ruby issue.

—Matt Jones

The = operator should still return whatever it sets, though, which would mean the two different accesses should return the same thing. And I believe this is the case currently. To not do so would be a legitimate bug.

What version of Rails is the example using?

I am seeing this with Rails 4.2.4.

Thanks Matt, your analysis is spot on. So the root cause actually a core behavior of Ruby. I found this pretty good explanation too on StackOverflow:

So, it looks like this cannot be fixed, huh…