README.md in depth-0.0.1 vs README.md in depth-0.0.2

- old
+ new

@@ -1,3 +1,242 @@ # Depth +Depth is a utility gem for deep manipulation of complex hashes, that +is nested hash and array structures. As you have probably guessed it +was originally created to deal with a JSON like document structure. +Importantly it uses a non-recursive approach to its enumeration. + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'depth' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install depth + +## Usage + +### The complex hash + +```ruby + hash = { '$and' => [ + { '#weather' => { 'something' => [], 'thisguy' => 4 } }, + { '$or' => [ + { '#otherfeed' => {'thing' => [] } }, + ]} + ]} +``` +_Nicked from a query engine we're using on Driftrock_ + +The above is a sample complex hash, to use the gem to +start manipulating it is pretty simple: + +```ruby + complex_hash = Depth::ComplexHash.new(hash) +``` + +Not exactly rocket science (not even data science). You +can retrieve the hash with either `base` or `to_h`. + +### Manipulation + +Manipulation of the hash is done using routes. A route +being a description of how to traverse the hash to +get to this point. + +The messages signatures relating to manipulation are: + +* `set(route, value)` = Set a value +* `find(route)` = Find a value +* `alter(route, key:)` = Alter a key (the last key in route) +* `alter(route, value:)` = Alter a value, identical to `set` +* `alter(route, key: value:)` = Alter a key and value, identical to a `set` and then `delete` +* `delete(route)` = Delete a value + +Routes can be defined as an array of keys or indeces: + +```ruby + hash = { '$and' => [ + { '#weather' => { 'something' => [], 'thisguy' => 4 } }, + { '$or' => [ + { '#otherfeed' => {'thing' => [] } }, + ]} + ]} + route = ['$and', 1, '$or', 0, '#otherfeed', 'thing'] + Depth::ComplexHash.new(hash).find(route) # => [] +``` + +But there's something cool hidden in the `set` message, +if part of the structure is missing, it'll fill it in as it +goes, e.g.: + +```ruby + hash = { '$and' => [ + { '#weather' => { 'something' => [], 'thisguy' => 4 } }, + { '$or' => [ + { '#otherfeed' => {'thing' => [] } }, + ]} + ]} + route = ['$and', 1, '$or', 0, '#sup', 'thisthing'] + Depth::ComplexHash.new(hash).set(route, 'hello') + puts hash.inspect #=> + # hash = { '$and' => [ + # { '#weather' => { 'something' => [], 'thisguy' => 4 } }, + # { '$or' => [ + # { '#otherfeed' => {'thing' => [] } }, + # { '#sup' => {'thisthing' => 'hello' } }, + # ]} + # ]} +``` + +Great if you want it to be a hash, but what if you want to add +an array, no worries, just say so in the route: + +```ruby + route = ['$and', 1, '$or', 0, ['#sup', :array], 0] + # Routes can also be defined in other ways + route = ['$and', 1, '$or', 0, { key: '#sup', type: :array }, 0] + route = ['$and', 1, '$or', 0, RouteElement.new('#sup', type: :array), 0] +``` + +### Enumeration + +The messages signatures relating to enumeration are: + +* `each` = yields `key_or_index` and `fragment`, returns the complex hash +* `map` = yields `key_or_index`, `fragment` and `parent_type`, returns a new complex hash +* `map_values` = yields `fragment`, returns a new complex hash +* `map_keys` = yields `key_or_index`, returns a new complex hash +* `map!`, `map_keys!` and `map_keys_and_values!`, returns a new complex hash +* `reduce(memo)` = yields `memo`, `key` and `fragment`, returns memo +* `each_with_object(obj)` = yields `key`, `fragment` and `object`, returns object + +_Fragment refers to a chunk of the original hash_ + +These, perhaps, require a bit more explanation: + +#### each + +The staple, and arguably the most important, of all the enumeration methods, + +```ruby + hash = { ... } + Depth::ComplexHash.new(hash).each { |key, fragment| } +``` + +Each yields keys and associated fragments from the leaf nodes +backwards. For example, the hash: + +```ruby + { '$and' => [{ 'something' => { 'x' => 4 } }] } +``` + +would yield: + +1. `x, 4` +2. `something, { "x" => 4 }` +3. `0, { "something" => { "x" => 4 } }` +4. `$and, [{ "something" => { "x" => 4 } }]` + + +#### map + +Map yields both the current key/index and the current fragment, +expecting both returned in an array. I've yet to decide if +there should be a third argument that tells you whether or not +the key/index is for an array or a hash. I've not needed it +but I suspect it might be useful. If it comes up I'll add it. + + +```ruby + hash = { '$and' => [{ 'something' => { 'x' => 4 } }] } + Depth::ComplexHash.new(hash).map do |key, fragment| + [key, fragment] + end +``` + +like `each` the above would yield: + +1. `x, 4` +2. `something, { "x" => 4 }` +3. `0, { "something" => { "x" => 4 } }` +4. `$and, [{ "something" => { "x" => 4 } }]` + +and with the contents being unchanged it would return a +new complex hash with equal contents to the current one. + +#### map_values + +```ruby + hash = { '$and' => [{ 'something' => { 'x' => 4 } }] } + Depth::ComplexHash.new(hash).map_values do |fragment| + fragment + end +``` + +This will yield only the fragments from `map`, useful if +you only wish to alter the value parts of the hash. + +#### map_keys + +```ruby + hash = { '$and' => [{ 'something' => { 'x' => 4 } }] } + Depth::ComplexHash.new(hash).map_keys do |key| + key + end +``` + +This will yield only the keys from `map`, useful if +you only wish to alter the keys. + +#### map!, map_keys!, map_values! + +The same as their non-exclamation marked siblings save that +they will cause the complex hash on which they operate to change. + +#### reduce and each_with_object + +Operate as you would expect. Can I take a moment to point out how +irritating it is that `each_with_object` yields the object you pass +in as its last argument while `reduce` yields it as its first O_o? + +```ruby + hash = { '$and' => [{ 'something' => { 'x' => 4 } }] } + Depth::ComplexHash.new(hash).reduce(0) do |memo, key, fragment| + memo += 1 + end + + Depth::ComplexHash.new(hash).each_with_object([]) do |key, fragment, obj| + obj << key + end +``` + +## Why? + +Alright, we needed to be able to find certain keys +from all the keys contained within the complex hash as said keys +were the instructions as to what data the hash would be able to match +against. This peice of code was originally recursive. We were adding +a feature that required us to also be able to edit these keys, mark +them with a unique identifier. As I was writing this I decided I wasn't +happy with the recursive nature of the key search as we have no guarantees +about how nested the hash could be. As I refactored the find and built +the edit it became obvious that the code wasn't tied to the project at +hand so I refactored it out to here. + + +## Contributing + +1. Fork it ( https://github.com/[my-github-username]/depth/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request