This gem provides the walk_objets function, ie a map like function to any arbitrary tree. This can be used either to generate a flat list of an object and its dependencies or to just to iterate recursively over them. Each object is only walked once, The tree is explored using a hash describing for each class, which methods to call. This can be either a symbol, a list of symbol or Proc to execute.
Lets consider a Rails like example
Class User has_many :posts end
:
Class Post :has_many :comments end
: :
user.walk_objects(:user => [:posts], :posts =>[:comments])
will generate a list containing, the user, all its posts, and all of the associated comments
to filter the element to be walked use a block. If a block return nil
then element and its dependencies are skiped. If the result is []
the element is not returned in the result but its dependencies are walked.
You can also return a Cut
object to return an objet without walking it.
With the previous example, to get only the list of comments
user.walk_objects(:user => :posts, :posts =>:comments) { |e| e.is_a?(Comment) ? e : []}
To not walk a particular user
me = User.find('me') user.walk_objects(:user => :posts, :posts =>:comments) { |e| e == me ? : RubyWalk::Cut.new(e) : e }
use :keys
and or :values
.
To get all the final value of a Hash
> {:a=>1, :b=>2, :c=>{:a=>3, :d=>5}}.walk_objects(Hash => :values){ |e| e.is_a?(Hash) ? [] : e } [1, 2, 3, 5]
by default a List walk over its elements and excludes itself.
> [[1, 2], [3, 2], [[5, 6, [7, 8]]]].walk_objects(){ |x| x*10 } [10, 20, 30, 50, 60, 70, 80]
You might need to have different dependencies for the same class depending of the depth or break a cycle in the dependencies. The easiest to do so it to chain walk (or do a multiplestep walk).
Imagine in the previous example , you want to walk through the users for each comments. Adding Comment => :users
like this
user.walk_objects(:user => :posts, :posts =>:comments, Comment => :users)
will cycle trough users and pull every posts from the users which had left a comment on our post. Instead chain 2 walks.
user.walk_objects(:user => :posts, :posts =>:comments).walk_objects(Comment => :users)
Only the root user, will have the posts walked through.
You can also pass an array of hashes to the walk methods
user.walk_objects([{:user => :posts, :posts =>:comments}, {Comment => :users}])
By default the methods described in the “model tree” for a subclass are merged with the superclass. To avoid that, use the special method :skip_super
.
class A def f ... end end class B < A def g ... end end
Walking with the parameter { :a => :f, :b=> :g }
will pull f, and g for the class B.
To pull only g , use { :a => :f, :b=> [:g, :skip_super] }
.
by passing extra arguments to a block, you can get usefull edge information as:
- parent
- index : rank of the current object within parent
- max_index : number total of sibbling
At the moment Array are not considered as a parent and set the parent of the Array if it exists.
> %w{a b c d}.walk_objects { |x, parent, index| {x => index }} [{"a"=>0}, {"b"=>1}, {"c"=>2}, {"d"=>3}]