Hash#collect, Hash#inject, and Ruby’s remarkable Enumerable module

I haven’t posted about Ruby in a while, in part because I’m excited about learning Clojure and (possibly) Haskell with a bunch of fellow language geeks (er, software professionals). But I do >50% of my daily work in Ruby, and I do think about it a lot. Today I was reminded about how much useful knowledge and mastery lies hidden in the corners of constructs one may think one knows well. The reminder came to me courtesy of Hash#collect, which returns an array, not a hash. Needing a hash from an operation on a hash, it was time to do a little digging and a little learning.

I was wrestling with RSpec, as I often am. :-) I have a spec helper with a hash of attributes that will create a valid instance of a class. What I wanted to do in a model test was create a few instances using the attributes hash to save typing, but I was running up against uniqueness requirements that prevented me from using the attributes hash repeatedly in individual specs. How could I mutate a copy of that hash so that its values were reliably different? Well, at first glance I thought collect would work. Except of course that collect returns an array. And I agree with others that it should return an array. That’s what collect does across Enumerable.

There have been people who think otherwise. Why the lucky stiff posted about monkeypatching Hash accordingly a few years ago, and there was an RCR request for a special method for Hash. But after a bit of thought, I realized there was a another way to go about it: Enumerbable#inject.

valid_attrs.inject({}) {|memo, (k,v)|
memo[k] = v+"1"; memo}

No method aliasing, or additions to the Ruby language. Just what inject is supposed to do: build something from an enumerable.

I may be naïve. Maybe a special function like map_to_hash would be more efficient. But everything is based on Enumerable#each, right? So how much worse could inject possibly be?

To generalize my attribute mutator a bit, here’s a little class that allows you to generate unique attribute values, using either a block or an integer:

class AttrMutator

def initialize(hsh, *iter_start)
  @hsh = hsh
  @iter = iter_start[0] || 0
end

def next
  if block_given?
    @hsh.inject({}) {| memo, (k, v) | memo[k] = yield v; memo}
  else
    @iter = @iter+1
    @hsh.inject({}) {| memo, (k, v) | memo[k] = v + (@iter.to_s); memo}
  end
end

end

mut = AttrMutator.new({:one => "uno", :two => "dos"})
mut.next # => {:one => "uno1", :two => "dos1"}
mut.next # => {:one => "uno2", :two => "dos2"}
mut.next {|v| v + "p"} # => {:one => "unop", :two => "dosp"}

I don’t feel especially smart to have figured this out for myself. Rather, I’m struck by how much power is made available by the Enumerable module and how far I am probably from learning it all. But it’s so much fun to have this little bitty powerful tools to play with.