Skip to content
Create a gist now

Instantly share code, notes, and snippets.

Embed URL


Subversion checkout URL

You can clone with
Download ZIP

We've seen lots of Ruby feature requests about converting an Enumerable to a Hash: to_h, map_hash, map_to, each_with_hash, etc. Let's look at one common, simple case of this.

Building a key/value mapping from a collection of keys is awkward. We commonly see Hash[* { |element| [element, calculate(element)] }] or collection.each_with_object({}) { |element, hash| hash[element] = calculate(element) }. Both are verbose. They require boilerplate code that's not relevant to the programmer's intent: to associate an enumerable of keys with calculated values.

Ruby has the idea of an association already: a key and value paired together. It's used by Array#assoc to look up a value from a list of pairs and by Hash#assoc to return a key/value pair. Building up a mapping of key/value pairs is associating keys with values.

So! Consider Enumerable#associate which builds a mapping by associating keys with values:

# Associate filenames with URLs. Before:
Hash[ { |filename| [ filename, download_url(filename) ]}]
# After:
filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

# Associate letters with their position in the alphabet. Before:
alphabet.each_with_index.each_with_object({}) { |(letter, index), hash| hash[letter] = index }
# After:
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

# A simple Hash#slice(*keys). Before:
keys.each_with_object({}) { |k, hash| hash[k] = self[k] }
# After:
keys.associate { |key| self[key] }

"Associate" is a simple verb with unsurprising results. You associate an enumerable of keys with yielded values.

module Enumerable
  # Associates keys with values and returns a Hash.
  # If you have an enumerable of keys and want to associate them with values,
  # pass a block that returns a value for the key:
  #   [1, 2, 3].associate { |i| i ** 2 }
  #   # => { 1 => 1, 2 => 4, 3 => 9 }
  #   %w( tender love ).associate &:capitalize
  #   # => {"tender"=>"Tender", "love"=>"Love"}
  # If you have an enumerable key/value pairs and want to associate them,
  # omit the block and you'll get a hash in return:
  #   [[1, 2], [3, 4]].associate
  #   # => { 1 => 2, 3 => 4 }
  def associate(mapping = {})
    if block_given?
      each_with_object(mapping) do |key, object|
        object[key] = yield(key)
      each_with_object(mapping) do |(key, value), object|
        object[key] = value

You should be able to shift the block_given? inside the each_with_object block because of the way |(a, b), c|-style args are handled.

def associate(mapping = {})
  each_with_object(mapping) do |(key, value), object|
    object[key] = block_given? ? yield(key) : value

Seems to work as expected. But maybe you prefer it the original way. :)

In case it isn't clear:

[1].each_with_object({}) { |(a, b), c| p [a, b, c] }
#=> [1, nil, {}]

See for Ruby feature proposal.


@aprescott, I think the idea is to only check block_given? once, even if that requires a more verbose method. Checking it once per iteration would give lower performance. But also note that this will probably be rewritten in C :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.