I think that's the best approach, but there's a subtle bug there that might
catch you depending on which hashes you're dealing with:
What you have works fine when the nesting is identical such as in your
example, but imagine if instead you have this:
h1 = {:key => {a: 1, b: 2, c: {a: 1, b: 2}}}
h2 = {:key => {c: {a: 'a', b: {x: 'y'}, c: 'c'}, d: 4}}
(Note the addition of b: {x: 'y'} in the middle, so hash nesting at that
point in h2 is one more than in h1)
If you do h1.merge(h2, &recursive_merge) it will fail:
TypeError: can't convert Fixnum into Hash
This is because the lambda is running with "new" as {x: 'y'} (which does
respond to merge) and "old" as 2, so it's calling {x: 'y'}.merge(2).
Interestingly the reverse operation (h2.merge(h1)) *would* work but
naturally has a different result.
So I think to be safe you need to do checking on both old and new:
recursive_merge = lambda{|k, old, new| new.respond_to?(:merge) &&
old.respond_to?(:merge) ? new.merge(old, &recursive_merge) : new}
Also I think it's safer to just check directly for Hash instead of using
respond_to?(:merge), as it's possible non-Hash objects will have defined a
"merge" method:
recursive_merge = lambda{|k, old, new| new === Hash && old === Hash ?
new.merge(old, &recursive_merge) : new}
It would be really nice if Ruby had some way to reference the defined lambda
within the block so we didn't have to assign it to a local variable for use
within the closure, something like:
lambda{|k, old, new| new === Hash && old === Hash ? new.merge(old, &self) :
new}
But of course self references the object in which the lambda was defined, so
that doesn't work.
On Thu, Dec 2, 2010 at 12:07 AM, Matt Aimonetti <[email protected]>wrote:
> Tonight I was trying to do some recursive merging of hashes and the only
> decent solution I came up with looks like that:
>
> h1 = {:key => {a: 1, b: 2, c: {a: 1, b: 2}}}
> h2 = {:key => {c: {a:'a', c: 'c'}, d: 4}}
>
> recursive_merge = lambda{|k, old, new| new.respond_to?(:merge) ?
> new.merge(old, &recursive_merge) : new}
> puts h1.merge(h2, &recursive_merge)
> # => {:key=>{:a=>1, :b=>2, :c=>{:a=>"a", :b=>2, :c=>"c"}, :d=>4}}
>
> http://gist.github.com/724900
>
> (Yes I'm using Ruby 1.9, and if you aren't yet, I'm sorry for you ;) )
>
> The code above seems to be working pretty well but I'm wondering if that's
> really the best way to do that, what do you guys think and can anyone come
> up with a better solution?
>
> Thanks,
>
> - Matt
>
> --
> SD Ruby mailing list
> [email protected]
> http://groups.google.com/group/sdruby
--
SD Ruby mailing list
[email protected]
http://groups.google.com/group/sdruby