Add Hashie::Extensions::DeepFind
This extension adds a `#deep_find` (aliased as `#deep_detect`) and a `#deep_find_all` (aliased as `#deep_select`) method to any Hash-like object. These methods perform a depth-first search on the object and its values and return either the first occurrence (for `#deep_find`) or an array of all occurrences (for `#deep_find_all`) within the nested structure of the hash. They work for nested Hash-like objects and nested Enumerables. [Closes #156]
This commit is contained in:
parent
05aee4d5b8
commit
36ece58665
|
@ -10,6 +10,7 @@
|
|||
* [#205](http://github.com/intridea/hashie/pull/205): Added Hashie::Extensions::Mash::SafeAssignment - [@michaelherold](https://github.com/michaelherold).
|
||||
* [#206](http://github.com/intridea/hashie/pull/206): Fixed stack overflow from repetitively including coercion in subclasses - [@michaelherold](https://github.com/michaelherold).
|
||||
* [#207](http://github.com/intridea/hashie/pull/207): Fixed inheritance of transformations in Trash - [@fobocaster](https://github.com/fobocaster).
|
||||
* [#209](http://github.com/intridea/hashie/pull/209): Added Hashie::Extensions::DeepFind - [@michaelherold](https://github.com/michaelherold).
|
||||
* Your contribution here.
|
||||
|
||||
## 3.2.0 (7/10/2014)
|
||||
|
|
26
README.md
26
README.md
|
@ -260,6 +260,32 @@ user.deep_fetch :name, :middle { |key| 'default' } # => 'default'
|
|||
user.deep_fetch :groups, 1, :name # => 'Open source enthusiasts'
|
||||
```
|
||||
|
||||
### DeepFind
|
||||
|
||||
This extension can be mixed in to provide for concise searching for keys within a deeply nested hash.
|
||||
|
||||
It can also search through any Enumerable contained within the hash for objects with the specified key.
|
||||
|
||||
Note: The searches are depth-first, so it is not guaranteed that a shallowly nested value will be found before a deeply nested value.
|
||||
|
||||
```ruby
|
||||
user = {
|
||||
name: { first: 'Bob', last: 'Boberts' },
|
||||
groups: [
|
||||
{ name: 'Rubyists' },
|
||||
{ name: 'Open source enthusiasts' }
|
||||
]
|
||||
}
|
||||
|
||||
user.extend Hashie::Extensions::DeepFind
|
||||
|
||||
user.deep_find(:name) #=> { first: 'Bob', last: 'Boberts' }
|
||||
user.deep_detect(:name) #=> { first: 'Bob', last: 'Boberts' }
|
||||
|
||||
user.deep_find_all(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
||||
user.deep_select(:name) #=> [{ first: 'Bob', last: 'Boberts' }, 'Rubyists', 'Open source enthusiasts']
|
||||
```
|
||||
|
||||
## Mash
|
||||
|
||||
Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended. It is intended to give the user easier access to the objects within the Mash through a property-like syntax, while still retaining all Hash functionality.
|
||||
|
|
|
@ -21,6 +21,7 @@ module Hashie
|
|||
autoload :StringifyKeys, 'hashie/extensions/stringify_keys'
|
||||
autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys'
|
||||
autoload :DeepFetch, 'hashie/extensions/deep_fetch'
|
||||
autoload :DeepFind, 'hashie/extensions/deep_find'
|
||||
autoload :PrettyInspect, 'hashie/extensions/pretty_inspect'
|
||||
autoload :KeyConversion, 'hashie/extensions/key_conversion'
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
module Hashie
|
||||
module Extensions
|
||||
module DeepFind
|
||||
# Performs a depth-first search on deeply nested data structures for
|
||||
# a key and returns the first occurrence of the key.
|
||||
#
|
||||
# options = {user: {location: {address: '123 Street'}}}
|
||||
# options.deep_find(:address) # => '123 Street'
|
||||
def deep_find(key)
|
||||
_deep_find(key)
|
||||
end
|
||||
|
||||
alias_method :deep_detect, :deep_find
|
||||
|
||||
# Performs a depth-first search on deeply nested data structures for
|
||||
# a key and returns all occurrences of the key.
|
||||
#
|
||||
# options = {users: [{location: {address: '123 Street'}}, {location: {address: '234 Street'}}]}
|
||||
# options.deep_find_all(:address) # => ['123 Street', '234 Street']
|
||||
def deep_find_all(key)
|
||||
matches = _deep_find_all(key)
|
||||
matches.empty? ? nil : matches
|
||||
end
|
||||
|
||||
alias_method :deep_select, :deep_find_all
|
||||
|
||||
private
|
||||
|
||||
def _deep_find(key, object = self)
|
||||
if object.respond_to?(:key?)
|
||||
return object[key] if object.key?(key)
|
||||
|
||||
reduce_to_match(key, object.values)
|
||||
elsif object.is_a?(Enumerable)
|
||||
reduce_to_match(key, object)
|
||||
end
|
||||
end
|
||||
|
||||
def _deep_find_all(key, object = self, matches = [])
|
||||
if object.respond_to?(:key?)
|
||||
matches << object[key] if object.key?(key)
|
||||
object.values.each { |v| _deep_find_all(key, v, matches) }
|
||||
elsif object.is_a?(Enumerable)
|
||||
object.each { |v| _deep_find_all(key, v, matches) }
|
||||
end
|
||||
|
||||
matches
|
||||
end
|
||||
|
||||
def reduce_to_match(key, enumerable)
|
||||
enumerable.reduce(nil) do |found, value|
|
||||
return found if found
|
||||
|
||||
_deep_find(key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Hashie::Extensions::DeepFind do
|
||||
subject { Class.new(Hash) { include Hashie::Extensions::DeepFind } }
|
||||
let(:hash) do
|
||||
{
|
||||
library: {
|
||||
books: [
|
||||
{ title: 'Call of the Wild' },
|
||||
{ title: 'Moby Dick' }
|
||||
],
|
||||
shelves: nil,
|
||||
location: {
|
||||
address: '123 Library St.',
|
||||
title: 'Main Library'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
let(:instance) { subject.new.update(hash) }
|
||||
|
||||
describe '#deep_find' do
|
||||
it 'detects a value from a nested hash' do
|
||||
expect(instance.deep_find(:address)).to eq('123 Library St.')
|
||||
end
|
||||
|
||||
it 'detects a value from a nested array' do
|
||||
expect(instance.deep_find(:title)).to eq('Call of the Wild')
|
||||
end
|
||||
|
||||
it 'returns nil if it does not find a match' do
|
||||
expect(instance.deep_find(:wahoo)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deep_find_all' do
|
||||
it 'detects all values from a nested hash' do
|
||||
expect(instance.deep_find_all(:title)).to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
|
||||
end
|
||||
|
||||
it 'returns nil if it does not find any matches' do
|
||||
expect(instance.deep_find_all(:wahoo)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue