Rail’s Enumerable module provides a number of time saving methods for dealing with array-like structures. The one that’s provided the most utility (for me at least) is index_with. I often find myself with an array of items I’d like to use as keys for a new nested hash. With index_with this is as simple as:

months = ['Jan', 'Feb', 'Mar']
months.index_with({})
=> { 'Jan' => {}, 'Feb' => {}, 'Mar' => {} }

This pattern has generally served me quite well, but there is a bug hidden in plain sight.

months['Jan'][:days] = 31
=> ?

If you, like me, assumed that the above snippet will set the value of :days in the ‘Jan’ hash to 31 you would only be partially correct. In fact, it sets the :days for ~every~ month to 31.

months['Jan'][:days] = 31
puts months
=> { 'Jan' => { days: 31 }, 'Feb' => { days: 31 }, 'Mar' => { days: 31 } }

So how do you get the intended result? You must use a new hash each time. Beneath the terse syntax, index_with is a simple each loop that builds a new hash with our provided default value:

def index_with(default = (no_default = true))
...
  result = {}
  each { |elem| result[elem] = default }
  result
...
end

When the provided default is a literal like {}, index_with reuses the same exact object each time. The result is that each of the month values are the exact same object:

month.values.map(&:object_id)
=> [29000, 29000, 29000]

Avoiding the literal here gives you the intended behavior:

months = ['Jan', 'Feb', 'Mar']
months.index_with(Array.new)
month.values.map(&:object_id)
=> [29001, 29002, 29003]