1
0
mirror of https://github.com/meineerde/rackstash.git synced 2025-10-17 14:01:01 +00:00

Add Fields::Hash#reverse_merge and reverse_merge! similar to the ActiveSupport methods on Hash

This commit is contained in:
Holger Just 2017-03-07 16:43:05 +01:00
parent 2070bf81a7
commit bfa5e33c04
2 changed files with 136 additions and 0 deletions

View File

@ -343,6 +343,66 @@ module Rackstash
end end
alias update merge! alias update merge!
# Returns a new {Hash} containing the contents of `hash` and the contents
# of `self`. `hash` is normalized before being added. In contrast to
# {#merge}, this method preserves any non-nil values of existing keys in
# `self` in the returned hash.
#
# If `hash` is a callable object (e.g. a proc or lambda), we will call
# it and use the returned value instead, which must then be a Hash of
# course. If any of the values of the hash is a proc, it will also be
# called and the return value will be used.
#
# If you give the optional `scope` argument, the Procs will be evaluated
# in the instance scope of this object. If you leave the `scope` empty,
# they will be called in the scope of their creation environment.
#
# @param hash (see #merge)
# @param scope (see #merge)
# @return [Rackstash::Fields::Hash] a new hash containing the merged
# key-value pairs
#
# @see #merge
# @see #reverse_merge!
def reverse_merge(hash, scope: nil)
merge(hash, force: false, scope: scope) { |_key, old_val, new_val|
old_val == nil ? new_val : old_val
}
end
# Adds the contents of `hash` to `self`. `hash` is normalized before being
# added. In contrast to {#merge!}, this method deep-merges Hash and Array
# values if the existing and merged values are of the same type.
#
# Returns a new {Hash} containing the contents of `hash` and the contents
# of `self`. `hash` is normalized before being added. In contrast to
# {#merge}, this method preserves any non-nil values of existing keys in
# `self`.
#
# If `hash` is a callable object (e.g. a proc or lambda), we will call
# it and use the returned value instead, which must then be a Hash of
# course. If any of the values of the hash is a proc, it will also be
# called and the return value will be used.
#
# If you give the optional `scope` argument, the Procs will be evaluated
# in the instance scope of this object. If you leave the `scope` empty,
# they will be called in the scope of their creation environment.
#
# @param hash (see #merge!)
# @param scope (see #merge!)
# @return [self]
#
# @see #merge!
# @see #reverse_merge
def reverse_merge!(hash, scope: nil)
merge!(hash, force: false, scope: scope) { |_key, old_val, new_val|
old_val == nil ? new_val : old_val
}
end
alias reverse_update reverse_merge!
# Set a `key` of `self` to the returned value of the passed block. # Set a `key` of `self` to the returned value of the passed block.
# If the key is forbidden from being set or already exists with a value # If the key is forbidden from being set or already exists with a value
# other than `nil`, the block will not be called and the value will not be # other than `nil`, the block will not be called and the value will not be

View File

@ -510,6 +510,82 @@ describe Rackstash::Fields::Hash do
end end
end end
describe '#reverse_merge' do
before do
hash['foo'] = 'bar'
end
it 'creates a new hash' do
expect(hash.reverse_merge(foo: :baz, beep: :boop)).not_to equal hash
expect(hash).not_to include 'beep'
end
it 'does not overwrite existing values' do
expect(hash.reverse_merge(foo: :baz, beep: :boop)['foo']).to eql 'bar'
end
it 'adds new values' do
expect(hash.reverse_merge(foo: :baz, beep: :boop)['beep']).to eql 'boop'
end
it 'evaluates procs' do
expect(hash.reverse_merge(-> { { beep: -> { self } } }, scope: 42)['beep'])
.to eql 42
end
it 'overwrites nil values' do
hash['beep'] = nil
expect(hash).to include 'beep'
expect(hash.reverse_merge(beep: :boop)['beep']).to eql 'boop'
end
it 'raises an error for non-hash arguments' do
expect { hash.reverse_merge [] }.to raise_error TypeError
expect { hash.reverse_merge nil }.to raise_error TypeError
expect { hash.reverse_merge false }.to raise_error TypeError
expect { hash.reverse_merge 'value' }.to raise_error TypeError
end
end
describe '#reverse_merge!' do
before do
hash['foo'] = 'bar'
end
it 'mutates the existing hash' do
expect(hash.reverse_merge!(foo: :baz, beep: :boop)).to equal hash
expect(hash).to include 'beep'
end
it 'does not overwrite existing values' do
expect(hash.reverse_merge!(foo: :baz, beep: :boop)['foo']).to eql 'bar'
end
it 'adds new values' do
expect(hash.reverse_merge!(foo: :baz, beep: :boop)['beep']).to eql 'boop'
end
it 'evaluates procs' do
expect(hash.reverse_merge!(-> { { beep: -> { self } } }, scope: 42)['beep'])
.to eql 42
end
it 'overwrites nil values' do
hash['beep'] = nil
expect(hash).to include 'beep'
expect(hash.reverse_merge!(beep: :boop)['beep']).to eql 'boop'
end
it 'raises an error for non-hash arguments' do
expect { hash.reverse_merge! [] }.to raise_error TypeError
expect { hash.reverse_merge! nil }.to raise_error TypeError
expect { hash.reverse_merge! false }.to raise_error TypeError
expect { hash.reverse_merge! 'value' }.to raise_error TypeError
end
end
describe '#set' do describe '#set' do
it 'allows to set a normalized value' do it 'allows to set a normalized value' do
expect(hash).to receive(:normalize).with(:value).and_call_original expect(hash).to receive(:normalize).with(:value).and_call_original