Blockify
The blockify gem solves some of the problems associated with complex hierarchical nested arrays and hashes.
It is possible to represent an HTML file as a series of Arrays and Hashes with very deep levels of Hashes within Arrays within Hashes.
Traversing such a tree is tricky at best. The methods of the blockify gem are included in Array, and Hash using a naming convention
that should not compete with anything. We don't need to concern ourselves with the complexity of the structure and can instead
focus on the base elements wherever they may be.
Installation
Add this line to your application's Gemfile:
gem 'blockify'
And then execute:
$ bundle
Or install it yourself as:
$ gem install blockify
Usage
This first release includes a module that inserts itself with the Array and Hash classes. This same module could be used to service custom container classes as well so long as they look Array-like or Hash-like. As a practical point of view, examine the following code:
require 'blockify'
nested_thingee = [0,1,2,[3,4,5,[6,7,8,[9,10,11,12,[13,{:a=>[16,17],:b=>[18,19,20]}]]]],14,15]
ary = nested_thingee.stringify_elements
# ary == ["0","1","2",["3","4","5",["6","7","8",["9","10","11","12",["13",{:a=>["16","17"], :b=>["18","19","20"]}]]]],"14","15"]
We don't need to worry about what the structure is, we can traverse the entire system with a single call.
Note that the returned structure is identical to the original.
The blockify gem uses recursion to step through the entire structure. If we peek inside the #stringify_elements method we see this:
def stringify_elements
blockify_elements {|elm| elm.to_s}
end
The #blockify_elements method travels through the structure and calls our block whenever it encounters an element that is not an Array or a Hash.
Whatever the block returns, the element in question will be modified with that result. In this case, each element is converted to a string.
All the recursion magic is performed by the gem.
Another tool is an element locator. This is best shown by example as follows:
require 'blockify'
nested_thingee = [0,1,2,[3,4,5,[6,7,8,[9,10,11,12,[13,{:a=>[16,17],:b=>[18,19,20]}]]]],14,15]
path = nested_thingee.find_element_path { |t| t == 17 } # path == [3, 3, 3, 4, 1, :a, 1]
elm = nested_thingee[3][3][3][4][1][:a][1] # elm == 17
# or easier ...
elm = nested_thingee.path_get path # elm == 17
# and we can change it too!
old = nested_thingee.path_put "Fred" path # old == 17
elm = nested_thingee.path_get path # elm =="Fred"
# nested_thingee == [0,1,2,[3,4,5,[6,7,8,[9,10,11,12,[13,{:a=>[16,"Fred"],:b=>[18,19,20]}]]]],14,15]
It is up to your imagination what you put in the block. When the block returns true, the recursion is done, and you get the path.
Each method in the blockify gem is imported into both Hash and Array.
The gem also includes three access methods that utilize the returned path. The above example shows two of them called #path_put and #path_get.
Instead of searching for the first find, we have a means of finding every match as well; this is called: #find_element_paths.
This methods returns an array of paths where each path is an array of indicies.
We can access everything with #paths_get which will return an array of every found element.
See the example below:
require 'blockify'
nested_thingee = [0,1,2,[13,4,15,[6,7,8,[9,10,11,12,[3,{:a=>[16,17],:b=>[18,19,20]}]]]],14,5]
paths = nested_thingee.find_element_paths { |t| (5..15).include? t }
paths.first # [3,0]
paths.last # [5]
paths[5] # [3, 3, 3, 0]
nested_thingee.path_get paths[5] # 9
nested_thingee.paths_get paths # [13, 15, 6, 7, 8, 9, 10, 11, 12, 14, 5]
One of the methods of of the blockify gem utilizes the #find_element_path is called #includify?.
This is defined as follows:
def includify?(search_string)
path = find_element_path { |elm| elm.to_s.include? search_string }
!path.empty?
end
When you have a deeply nested array_hash system, the standard #include? method is no help.
Instead of looking one-level deep for an exact match, #includify? examines each element
until it finds what it is looking for. Internally, we are looking for a substring on one of the elements.
See the example below:
require 'blockify'
nasty_array_hash = [{:quick=>{:a=>"tool", :b=>["xray","tent"]}, :quicker=>{:a=>:mess}, "green"=>"fox in socks", :empty=>[{}]},"fred"]
nasty_array_hash.includify? "tool" # true
nasty_array_hash.includify? "mess" # true
nasty_array_hash.includify? "quick" # false, :quick is a key, not an element
nasty_array_hash.includify? "green" # false, "green" is a key, not an element
nasty_array_hash.includify? "fox" # true, "fox" is a substring of "fox in socks"
ep = nasty_array_hash.find_element_path { |elm| elm.to_s.include? "fox"} # [0, "green"]
str = nasty_array_hash.path_get ep # str == "fox in socks"
The current methods for this first release are as follows:
# new_array_hash = ary_hash.blockify_elements { |elm| some_expression_using_elm_that_gets_saved_to_elm }
# ary_hash.blockify_elements! { your_block_here } # ... self-modified ary_hash
# path_array = ary_hash.find_element_path { |elm| some_true_false_method_here_operating_on_elm }
# result = ary_hash.stringify_elements # creates same structure with elements converted to strings
# ary_hash.stringify_elements! # self-modified version of the above
# result = ary_hash.inspectify_elements # all elements replaced with a call to #inspect
# ary_hash.inspectify_elements! # self-modified version of the above
# ary_hash.includify? substring # true if substring is found in one of the elements
# ary_hash.find_element_paths { ... } # returns array of found paths
# ary_hash.path_get path_array # returns the element or structure found by the path array ... path_get([]) returns the entire structure
# ary_hash.paths_get array_paths # paths looks like this: [[],[],[],[],...] returns an array of each path_get
# ary_hash.path_put(data, path) # replaces current path element with data, where path is an ordered list of keys
Development
I need to control this for the time being, so stay tuned! I will add more goodies in later releases.
License
The gem is available as open source under the terms of the MIT License.