Rehash
This gem allows you to transform a hash from one structure to another. For instance, to extract deeply nested values from it to a more convenient form with a simple and easy-to use mapping. Inspired by hash_mapper, but has a more DRY and robust API.
Do not be confused with Ruby's core Hash#rehash method. This gem has nothing to
do with it and is used solely for mapping values from source hash into another one.
Installation
Add this line to your application's Gemfile:
gem 're-hash', require: 'rehash'And then execute:
$ bundle
Or install it yourself as:
$ gem install re-hash
Usage
Considering we have a following hash:
hash = {
foo: {
bar: {
baz: 1,
bak: 2
}
},
foos: [
{ bar: { baz: '3-1' } },
{ bar: { baz: '3-2' } }
],
big_foo: {
nested: {
bar1: { baz: '4-1' },
bar2: { baz: '4-2' },
bar3: { baz: '4-3' }
}
},
config: [
{name: 'important_value', value: 'yes'},
{name: 'secondary_value', value: 'no'}
]
}Simple mapping
Simple mapping, provided as a mapping hash, allows to quickly map source hash values to new structure:
Rehash.map(hash,
'/foo/bar/baz' => '/faz',
'/big_foo/nested/bar1/baz' => '/baz1'
)
# => {:faz => 1, :baz1 => '4-1'}Block usage
Rehash.map method yield a Rehash::Mapper instance that allows you to apply multiple
mappings, as well as transform mapped values themselves:
Rehash.map(hash) do |m|
m.(
'/foo/bar/baz' => '/faz',
'/foo/bar/bak' => '/fak'
)
m.('/big_foo/nested/bar1/baz' => '/baz1') do |value|
value.to_i
end
m.('/foos' => '/foos') do |foos|
foos.map{ |item| Rehash.map(item, '/bar/baz' => '/value') }
end
end
# => {faz: 1, fak: 2, :baz1 => 4, :foos => [{:value => '3-1'}, {:value => '3-2'}]}In case if you need to do any additional manipulations over resulting returned hash,
you may access it via Mapper#result method (i.e. m.result in example above)
Accessing array items
By index
It is very easy to map values from items within array by accessing them by index:
Rehash.map(hash, '/foos[0]/bar/baz' => '/first_faz')
# => {:first_faz => '3-1'}By property lookup
It is also possible to access item within array by one of it's properties:
Rehash.map(hash, '/config[name:important_value]/value' => '/important')
# => {:important => 'yes'}Refinement (recommended usage)
Rehash also implements a Hash class refinement, using which is actually
a recommended way of using re-hash. Considering that #map and #rehash
methods are part of Ruby's Hash core functionality, Rehash allows to use
#map_with method for hash mappings.
using Rehash
hash.map_with('/foo/bar/baz' => '/faz') # => {:faz => 1}
# OR:
hash.map_with do |m|
m.('/foo/bar/bak' => '/fak') { |v| v * 2 }
end
# => {:fak => 4}HashExtension
In case if you don't want to use refinement and want to have #map_with method
globally available, you can extend Hash itself with core extension:
Hash.send(:include, HashExtension)
{foo: 'baz'}.map_with('/foo' => '/bar') # => {bar: 'baz'}Options
Rehash uses '/' as path delimiter by default, as well as it symbolizes resulting
keys. To use other options on a distinct map or map_with calls you have to use block form:
Rehash.map(hash, delimiter: '.', symbolize_keys: false) do |m|
m.('foo.bar.baz' => 'foo.baz')
)
# => {"foo" => {"baz" => 1}}Or you can set default options globally:
Rehash.default_options(delimiter: '.', symbolize_keys: false)
Rehash.map(hash, 'foo.bar.baz' => 'foo.baz') # => {"foo" => {"baz" => 1}}Default value
On mapping, for convenience, you can use default option that will be assigned
to value that is missing at the specified path in the source hash or is nil
before it is yielded to the block (if block is given):
Rehash.map(hash) do |m|
m.('/foo/bar/baz' => '/faz', '/missing' => '/bak', default: 5) do |value|
value * 2
end
end
# => {:faz => 2, :bar => 10}Helper methods
Mapper instance that is yielded to the block also has a couple of small helper
methods for dealing with arrays and deeply nested values to make things even more DRY.
-
map_array(from => to, &block)- used to map an array of items at pathfromto a pathto, yielding aMapperinstance for each item:
Rehash.map(hash) do |m|
m.map_array('/foos' => '/foos') do |im|
im.('/bar/baz' => '/value')
end
# is the same as:
m.('/foos' => '/foos') do |value|
value.map do |item|
Rehash.map(item, '/bar/baz' => '/value')
end
end
end-
map_hash(from => to, &block)- yields aMapperinstance for a hash located at the pathfromand puts result of mapping to the path defined byto:
Rehash.map(hash) do |m|
m.map_hash('/big_foo/nested' => '/') do |hm|
hm.(
'/bar1/baz' => '/big_baz1',
'/bar2/baz' => '/big_baz2',
'/bar3/baz' => '/big_baz3'
)
end
# is the same as:
m.(
'/big_foo/nested/bar1/baz' => '/big_baz1',
'/big_foo/nested/bar2/baz' => '/big_baz2',
'/big_foo/nested/bar3/baz' => '/big_baz3'
)
end-
[](path)and[]=(path, value)- small helper methods that can be called on mapper object when hash is mapped with a block form.[](path)can be used to get the value at thepathin source hash, and[]=(path, value)can be used to put thevalueat thepathin the resulting hash:
Rehash.map(hash) do |m|
m['/faz'] = m['/foo/bar/baz']
# is essentially the same as:
m.('/foo/bar/baz' => '/faz')
endDevelopment
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console for an interactive prompt that will allow
you to experiment.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/akuzko/rehash.
License
The gem is available as open source under the terms of the MIT License.