Skip to content

Commit

Permalink
Add block to Extensions::DeepMerge
Browse files Browse the repository at this point in the history
  • Loading branch information
Ilya Kamenko committed Dec 30, 2014
1 parent 337a980 commit 2846b3e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@
* [#252](https://github.com/intridia/hashie/pull/252): Add support for conditionally required Hashie::Dash attributes - [@ccashwell](https://github.com/ccashwell).
* [#256](https://github.com/intridia/hashie/pull/256): Inherit key coercions - [@Erol](https://github.com/Erol).
* [#259](https://github.com/intridia/hashie/pull/259): Fix handling of default proc values in Mash - [@Erol](https://github.com/Erol).
* [#260](https://github.com/intridia/hashie/pull/260): Add block to Extensions::DeepMerge - [@galathius](https://github.com/galathius).
* Your contribution here.

## 3.3.2 (11/26/2014)
Expand Down
15 changes: 15 additions & 0 deletions README.md
Expand Up @@ -230,6 +230,21 @@ h1.deep_merge(h2) # => { x: { y: [7, 8, 9] }, z: "xyz" }
h2.deep_merge(h1) # => { x: { y: [4, 5, 6] }, z: [7, 8, 9] }
```

Like with Hash#merge in the standard library, a block can be provided to merge values:

```ruby
class MyHash < Hash
include Hashie::Extensions::DeepMerge
end

h1 = MyHash[{ a: 100, b: 200, c: { c1: 100 } }]
h2 = MyHash[{ b: 250, c: { c1: 200 } }]

h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
# => { a: 100, b: 450, c: { c1: 300 } }
```


### DeepFetch

This extension can be mixed in to provide for safe and concise retrieval of deeply nested hash values. In the event that the requested key does not exist a block can be provided and its value will be returned.
Expand Down
29 changes: 17 additions & 12 deletions lib/hashie/extensions/deep_merge.rb
Expand Up @@ -2,28 +2,33 @@ module Hashie
module Extensions
module DeepMerge
# Returns a new hash with +self+ and +other_hash+ merged recursively.
def deep_merge(other_hash)
dup.deep_merge!(other_hash)
def deep_merge(other_hash, &block)
dup.deep_merge!(other_hash, &block)
end

# Returns a new hash with +self+ and +other_hash+ merged recursively.
# Modifies the receiver in place.
def deep_merge!(other_hash)
_recursive_merge(self, other_hash)
def deep_merge!(other_hash, &block)
return self unless other_hash.is_a?(::Hash)
_recursive_merge(self, other_hash, &block)
self
end

private

def _recursive_merge(hash, other_hash)
if other_hash.is_a?(::Hash) && hash.is_a?(::Hash)
other_hash.each do |k, v|
hash[k] = hash.key?(k) ? _recursive_merge(hash[k], v) : v
end
hash
else
other_hash
def _recursive_merge(hash, other_hash, &block)
other_hash.each do |k, v|
hash[k] = if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
_recursive_merge(hash[k], v, &block)
else
if hash.key?(k) && block_given?
block.call(k, hash[k], v)
else
v
end
end
end
hash
end
end
end
Expand Down
39 changes: 31 additions & 8 deletions spec/hashie/extensions/deep_merge_spec.rb
Expand Up @@ -7,16 +7,39 @@ class DeepMergeHash < Hash

subject { DeepMergeHash }

let(:h1) { subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } }) }
let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } } } }
let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } } } }
it 'should return initial hash for arguments that are not hash' do
hash = subject.new.merge(a: 'a')
expect(hash.deep_merge('abc')).to eq(hash)
end

context 'without &block' do
let(:h1) { subject.new.merge(a: 'a', a1: 42, b: 'b', c: { c1: 'c1', c2: { a: 'b' }, c3: { d1: 'd1' } }) }
let(:h2) { { a: 1, a1: 1, c: { c1: 2, c2: 'c2', c3: { d2: 'd2' } } } }
let(:expected_hash) { { a: 1, a1: 1, b: 'b', c: { c1: 2, c2: 'c2', c3: { d1: 'd1', d2: 'd2' } } } }

it 'deep merges two hashes' do
expect(h1.deep_merge(h2)).to eq expected_hash
end

it 'deep merges two hashes' do
expect(h1.deep_merge(h2)).to eq expected_hash
it 'deep merges another hash in place via bang method' do
h1.deep_merge!(h2)
expect(h1).to eq expected_hash
end
end

it 'deep merges another hash in place via bang method' do
h1.deep_merge!(h2)
expect(h1).to eq expected_hash
context 'with &block' do
let(:h1) { subject.new.merge(a: 100, b: 200, c: { c1: 100 }) }
let(:h2) { { b: 250, c: { c1: 200 } } }
let(:expected_hash) { { a: 100, b: 450, c: { c1: 300 } } }
let(:block) { proc { |_, this_val, other_val| this_val + other_val } }

it 'deep merges two hashes' do
expect(h1.deep_merge(h2, &block)).to eq expected_hash
end

it 'deep merges another hash in place via bang method' do
h1.deep_merge!(h2, &block)
expect(h1).to eq expected_hash
end
end
end

0 comments on commit 2846b3e

Please sign in to comment.