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.