Better Hash Injection using each_with_object

A common Ruby pattern for injecting values from an Array into to a Hash is to use the Enumerable#inject method and pass the hash as the memo. If you had an enumeration of User objects, you might convert them to a hash with something like this:

  # build a hash of { name => email }
  users = User.all

  users.inject({}) do |memo, user|
    memo[user.name] = user.email
    memo # you must return the memo each time!
  end

While this will reduce the Array down to a Hash, it has the gotcha that you must return the memo each time. Enumerable#inject sets the memo to the return value of the block. If you forget to return the memo hash, your next memo will be set to the result of the assignment which is user.email.

There are some alternatives you can use to avoid the explicit return for the memo. Hash#merge is a popular choice. Hash#merge will combine the current hash with the new values and return a new hash. Which sets the memo to the new hash:

  # build a hash of { name => email }
  users = User.all

  users.inject({}) do |memo, user|
    memo.merge(user.name => user.email)
  end

Hash#merge returns a hash lest you forget to return the memo. It could be argued that using merge is not clear in this use case. Using plain merge also creates a new hash each time. This could be mitigated with the Hash#merge! which updates and returns the current hash. But it does not clean up our ruby code. We'd prefer if our intentions were presented in a clear and concise way.

Why are we so intent to use the Enumerable#inject method when building hashes? Enumerable#inject is wonderful when reducing a list, like determining the sum of values from the list. For example:

   # inject is great for reducing a list to a value
   users = User.all
   sum = users.inject(0) { |sum, user| sum + user.age }

But not so great when building a hash (see above). There is another way! Enumerable also has a less commonly used method: Enumerable#each_with_object, which solves our issues. Enumerable#each_with_object passes the same memo to each iteration of the block. It does not reassign it to the return value. This gives us the behavior we expected with Enumerable#inject but without the caveats.

  users.each_with_object({}) do |user, memo| # note object and memo reversed from #inject
    memo[user.name] = user.email
  end

Ah! Nice and clean. Our intentions are clear. The code in the block is exactly what you would expect. The block is called for each element and the same memo object is passed in for each iteration. You set the name/value in the hash and need not concern yourself with the return value.

Using Enumerable#inject to build a hash never felt comfortable. It always felt like a hack. Ruby's Enumerable#inject was never a good fit for Hash building, but Enumerable#each_with_object fits just right.

Enumerable is a powerful mixin. Even after many years of coding with it in Ruby, we find new ways of expressing ourselves and revealing our intentions. Next time you have to reduce an Array to a Hash, don't fall into the #inject trap. Use Enumerable#each_with_object and impress your friends and family.

by Chris Mar