32

I seem to run into this very often. I need to build a Hash from an array using an attribute of each object in the array as the key.

Lets say I need a hash of example uses ActiveRecord objecs keyed by their ids Common way:

ary = [collection of ActiveRecord objects]
hash = ary.inject({}) {|hash, obj| hash[obj.id] = obj }

Another Way:

ary = [collection of ActiveRecord objects]
hash = Hash[*(ary.map {|obj| [obj.id, obj]}).flatten]

Dream Way: I could and might create this myself, but is there anything in Ruby or Rails that will this?

ary = [collection of ActiveRecord objects]
hash = ary.to_hash &:id
#or at least
hash = ary.to_hash {|obj| obj.id}
xan
  • 7,440
  • 8
  • 43
  • 65
Daniel Beardsley
  • 19,907
  • 21
  • 66
  • 79

5 Answers5

67

There is already a method in ActiveSupport that does this.

['an array', 'of active record', 'objects'].index_by(&:id)

And just for the record, here's the implementation:

def index_by
  inject({}) do |accum, elem|
    accum[yield(elem)] = elem
    accum
  end
end

Which could have been refactored into (if you're desperate for one-liners):

def index_by
  inject({}) {|hash, elem| hash.merge!(yield(elem) => elem) }
end
August Lilleaas
  • 54,010
  • 13
  • 102
  • 111
  • 1
    I think if you change the merge to merge! you'll avoid creating a bunch of intermediate hashes you don't need. – Scott Jan 05 '09 at 15:26
  • If you are going to use this many times in the critical path of you app, you might want to consider using ary.index_by{|o| o.id} instead of using symbol_to_proc. – krusty.ar Jan 05 '09 at 19:08
  • `index_by` seems to be a duplication of Ruby's `group_by`. Am I missing anything? – JMH Jan 26 '15 at 22:54
  • 2
    group_by has an array as the value, whereas index_by assumes there's only one item per key so the value is that single item, not an array. – August Lilleaas Jan 27 '15 at 08:21
  • `['an array', 'of active record', 'objects'].index_by(&:id)` fails with error NoMethodError: undefined method `id' for "an array":String using Rails 4.2.5 and Ruby 2.3.0 – Sven R. Feb 24 '16 at 16:33
  • The example is not self contained, the strings are representing, as their contents suggest, active record objects, which has an id method :) – August Lilleaas Feb 25 '16 at 09:11
10

a shortest one?

# 'Region' is a sample class here
# you can put 'self.to_hash' method into any class you like 

class Region < ActiveRecord::Base
  def self.to_hash
    Hash[*all.map{ |x| [x.id, x] }.flatten]
  end
end
zed_0xff
  • 32,417
  • 7
  • 53
  • 72
9

In case someone got plain array

arr = ["banana", "apple"]
Hash[arr.map.with_index.to_a]
 => {"banana"=>0, "apple"=>1}
Fedcomp
  • 2,175
  • 5
  • 21
  • 23
5

You can add to_hash to Array yourself.

class Array
  def to_hash(&block)
    Hash[*self.map {|e| [block.call(e), e] }.flatten]
  end
end

ary = [collection of ActiveRecord objects]
ary.to_hash do |element|
  element.id
end
ewalshe
  • 3,684
  • 2
  • 19
  • 19
0

Install the Ruby Facets Gem and use their Array.to_h.

Lolindrath
  • 2,101
  • 14
  • 20