diff --git a/lib/rackstash/fields/hash.rb b/lib/rackstash/fields/hash.rb index 6684104..088400a 100644 --- a/lib/rackstash/fields/hash.rb +++ b/lib/rackstash/fields/hash.rb @@ -10,6 +10,8 @@ require 'rackstash/fields/abstract_collection' module Rackstash module Fields class Hash < AbstractCollection + include ::Enumerable + # @return [Set] a frozen list of strings which are not allowed to # be used as keys in this hash. attr_reader :forbidden_keys @@ -224,6 +226,59 @@ module Rackstash merge!(hash, force: force, scope: scope, &resolver) end + # Calls the given block once for each key in the hash, passing the + # key-value pair as parameters. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @yield [key, value] calls the given block once for each key in the hash + # @yieldparam key [String] the yielded key + # @yieldparam value [Object] the yielded value + # @return [Enumerator, self] `self` if a block was given or an + # `Enumerator` if no block was given. + def each + return enum_for(__method__) unless block_given? + @raw.each_pair do |key, value| + yield key, value + end + self + end + alias each_pair each + + # Calls the given block once for each key in the hash, passing the key as + # a parameter. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @yield [key] calls the given block once for each key in the hash + # @yieldparam key [String] the yielded key + # @return [Enumerator, self] `self` if a block was given or an + # `Enumerator` if no block was given. + def each_key + return enum_for(__method__) unless block_given? + @raw.each_key do |key| + yield key + end + self + end + + # Calls the given block once for each key in the hash, passing the value + # at the respective key as a parameter. + # + # If no block is given, an `Enumerator` is returned instead. + # + # @yield [value] calls the given block once for each key in the hash + # @yieldparam value [Object] the yielded value of the key + # @return [Enumerator, self] `self` if a block was given or an + # `Enumerator` if no block was given. + def each_value + return enum_for(__method__) unless block_given? + @raw.each_value do |value| + yield value + end + self + end + # @return [Boolean] `true` if the Hash contains no ley-value pairs, # `false` otherwise. def empty? diff --git a/spec/rackstash/fields/hash_spec.rb b/spec/rackstash/fields/hash_spec.rb index deda102..a4bf71a 100644 --- a/spec/rackstash/fields/hash_spec.rb +++ b/spec/rackstash/fields/hash_spec.rb @@ -13,6 +13,12 @@ describe Rackstash::Fields::Hash do let(:forbidden_keys) { Set.new } let(:hash) { Rackstash::Fields::Hash.new(forbidden_keys: forbidden_keys) } + it 'is an Enumerable' do + expect(described_class).to be < Enumerable + expect(::Enumerable.public_instance_methods - hash.methods) + .to be_empty + end + describe '#initialize' do it 'can be initialized without any arguments' do Rackstash::Fields::Hash.new @@ -383,6 +389,71 @@ describe Rackstash::Fields::Hash do end end + describe '#each' do + it 'yields each key-value pair' do + hash['foo'] = 'string' + hash[:bar] = 42 + + expect { |b| hash.each(&b) } + .to yield_successive_args(['foo', 'string'], ['bar', 42]) + end + + it 'returns the hash if a block was provided' do + hash['foo'] = 'bar' + expect(hash.each {}).to equal hash + end + + it 'returns an Enumerator if no block was provided' do + hash['foo'] = 'bar' + expect(hash.each).to be_instance_of Enumerator + end + + it 'can use the alias #each_pair' do + expect(hash.method(:each_pair)).to eql hash.method(:each) + end + end + + describe '#each_key' do + it 'yields each key' do + hash['foo'] = 'string' + hash[:bar] = 42 + + expect { |b| hash.each_key(&b) } + .to yield_successive_args('foo', 'bar') + end + + it 'returns the hash if a block was provided' do + hash['foo'] = 'bar' + expect(hash.each_key {}).to equal hash + end + + it 'returns an Enumerator if no block was provided' do + hash['foo'] = 'bar' + expect(hash.each_key).to be_instance_of Enumerator + end + end + + describe '#each_value' do + it 'yields each value' do + hash['foo'] = 'string' + hash[:bar] = 42 + hash['double'] = 'string' + + expect { |b| hash.each_value(&b) } + .to yield_successive_args('string', 42, 'string') + end + + it 'returns the hash if a block was provided' do + hash['foo'] = 'bar' + expect(hash.each_value {}).to equal hash + end + + it 'returns an Enumerator if no block was provided' do + hash['foo'] = 'bar' + expect(hash.each_value).to be_instance_of Enumerator + end + end + describe '#empty?' do it 'returns true of there are any fields' do expect(hash.empty?).to be true