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