Generic traverse method for hash-ish structures
module Traversable
module InstanceMethods
def map_recursive(*args, &block)
Traversable.map_recursive(self, *args, &block)
end
end
class << self
def included(other)
other.send(:include, InstanceMethods)
end
# recursively map a hash-like structure with keys and
# arbitrary nested values
def map_recursive(thing, *args, &block)
case thing
when Hash
thing.each_with_object({}) do |(k, v), h|
h[k] = case v
when Hash
map_recursive(v, *args, &block)
when Array
v.map { |e| map_recursive(e, *args, &block) }
else
block.call(v, *args)
end
end
else
block.call(thing, *args)
end
end
def insinuate(target)
target.send(:include, Traversable)
end
end
end
### specs to show usage
describe Traversable do
it "should allow recursive traversal of nested hashes with any values" do
some_struct = {
'l1_int' => 1,
'l1_str' => 'kissa',
'left_node' => { 'l2_int' => 42, 'l2_str' => 'meow' },
'right_node' => { 'r2_int' => 10, 'r2_str' => 'woof', 'l3_ary' => ['foo', 1] },
:exclamation => 'quux'
}
capitalize_or_multiply = lambda { |e|
e.respond_to?(:capitalize) ? e.capitalize : e * 2
}
Traversable.map_recursive(some_struct, &capitalize_or_multiply).should == {
'l1_int' => 2,
'l1_str' => 'Kissa',
'left_node' => { 'l2_int' => 84, 'l2_str' => 'Meow' },
'right_node' => { 'r2_int' => 20, 'r2_str' => 'Woof', 'l3_ary' => ['Foo', 2] },
:exclamation => 'Quux'
}
end
it "should mixin with a Hash" do
Traversable.insinuate(Hash)
{:key => {:subkey => [1, 2], :a_key => 3}}.
map_recursive {|i| 2*i}.
should == {:key => {:subkey => [2, 4], :a_key => 6}}
end
end