** Installation
Install as a standalone gem
#+BEGIN_SRC shell
$ gem install tree_support
#+END_SRC
Or install within application using Gemfile
#+BEGIN_SRC shell
$ bundle add tree_support
$ bundle install
#+END_SRC
** How to use in one line
Just pass the object that has the parent, children method to =TreeSupport.tree=
#+BEGIN_SRC ruby
require "tree_support"
puts TreeSupport.tree(TreeSupport.example)
>> root
>> ├─Battle
>> │ ├─Attack
>> │ │ ├─Shake the sword
>> │ │ ├─Attack magic
>> │ │ │ ├─Summoned Beast X
>> │ │ │ └─Summoned Beast Y
>> │ │ └─Repel sword in length
>> │ └─Defense
>> ├─Withdraw
>> │ ├─To stop
>> │ │ ├─Place a trap
>> │ │ └─Shoot a bow and arrow
>> │ └─To escape
>> └─Break
>> ├─Stop
>> └─Recover
>> ├─Recovery magic
>> └─Drink recovery medicine
#+END_SRC
** Detailed usage
*** Prepare a node class like this
#+BEGIN_SRC ruby
class Node
attr_accessor :name, :parent, :children
def initialize(name = nil, &block)
@name = name
@children = []
if block_given?
instance_eval(&block)
end
end
def add(*args, &block)
tap do
children << self.class.new(*args, &block).tap { |v| v.parent = self }
end
end
end
#+END_SRC
*** Create a tree
#+BEGIN_SRC ruby
root = Node.new("root") do
add "Battle" do
add "Attack" do
add "Shake the sword"
add "Attack magic" do
add "Summoned Beast X"
add "Summoned Beast Y"
end
add "Repel sword in length"
end
add "Defense"
end
add "Withdraw" do
add "To stop" do
add "Place a trap"
add "Shoot a bow and arrow"
end
add "To escape"
end
add "Break" do
add "Stop"
add "Recover" do
add "Recovery magic"
add "Drink recovery medicine"
end
end
end
#+END_SRC
*** Visualization
#+BEGIN_SRC ruby
puts TreeSupport.tree(root)
>> root
>> ├─Battle
>> │ ├─Attack
>> │ │ ├─Shake the sword
>> │ │ ├─Attack magic
>> │ │ │ ├─Summoned Beast X
>> │ │ │ └─Summoned Beast Y
>> │ │ └─Repel sword in length
>> │ └─Defense
>> ├─Withdraw
>> │ ├─To stop
>> │ │ ├─Place a trap
>> │ │ └─Shoot a bow and arrow
>> │ └─To escape
>> └─Break
>> ├─Stop
>> └─Recover
>> ├─Recovery magic
>> └─Drink recovery medicine
#+END_SRC
*** Troublesome writing TreeSupport.tree
=include TreeSupport::Stringify=
#+BEGIN_SRC ruby
Node.include(TreeSupport::Stringify)
puts root.to_s_tree
>> root
>> ├─Battle
>> │ ├─Attack
>> │ │ ├─Shake the sword
>> │ │ ├─Attack magic
>> │ │ │ ├─Summoned Beast X
>> │ │ │ └─Summoned Beast Y
>> │ │ └─Repel sword in length
>> │ └─Defense
>> ├─Withdraw
>> │ ├─To stop
>> │ │ ├─Place a trap
>> │ │ └─Shoot a bow and arrow
>> │ └─To escape
>> └─Break
>> ├─Stop
>> └─Recover
>> ├─Recovery magic
>> └─Drink recovery medicine
#+END_SRC
*** How do I change the label of a node?
We look for =to_s_tree_name=, =name=, =subject=, =title=, =to_s= defined by =TreeSupport.name_methods= in that order, so we define the method by considering the priority
*** How do I change labels without defining methods?
Add a block to tree
#+BEGIN_SRC ruby
puts TreeSupport.tree(root) { |node| node.object_id }
>> 70308514816100
>> ├─70308514815920
>> │ ├─70308514815780
>> │ │ ├─70308514815680
>> │ │ ├─70308514815580
>> │ │ │ ├─70308514815480
>> │ │ │ └─70308514815420
>> │ │ └─70308514815360
>> │ └─70308514815300
>> ├─70308514815220
>> │ ├─70308514815080
>> │ │ ├─70308514814980
>> │ │ └─70308514814920
>> │ └─70308514814860
>> └─70308514814780
>> ├─70308514814680
>> └─70308514814580
>> ├─70308514814480
>> └─70308514814420
#+END_SRC
*** How to use methods that are common in tree structure?
The following methods become available in include of =TreeSupport::Treeable=
- root
- root?
- leaf?
- each
- each_node
- descendants
- self_and_descendants
- ancestors
- self_and_ancestors
- siblings
- self_and_siblings
*** How to convert to Gviz object?
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root)
#+END_SRC
*** How to image it?
#+BEGIN_SRC ruby
gv.output("tree.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/tree.png]]
*** How do I change the color of a particular node?
Return the graphviz attribute as a hash in TreeSupport.graphviz block
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root) do |node|
if node.name.include?("Attack")
{fillcolor: "lightblue", style: "filled"}
elsif node.name.include?("Recover")
{fillcolor: "lightpink", style: "filled"}
end
end
gv.output("tree_color.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/tree_color.png]]
*** How do I change the label of a particular node?
As with the above method, it returns a hash containing the label value
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root) do |node|
{label: node.name.chars.first}
end
gv.output("tree_label.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/tree_label.png]]
*** How can I check the dot format of Graphviz?
#+BEGIN_SRC ruby
puts gv.to_dot
>> digraph n70146110700700 {
>> graph [charset = "UTF-8", rankdir = "LR"];
>> n70146110700700 [label = "root"];
>> n70146110700700 -> {n70146110698600; n70146110691220; n70146110689500;};
>> n70146110698600 [label = "Battle"];
>> n70146110698600 -> {n70146110698320; n70146110691720;};
>> n70146110698320 [label = "Attack"];
>> n70146110698320 -> {n70146110697900; n70146110697240; n70146110692060;};
>> n70146110697900 [label = "Shake the sword"];
>> n70146110697240 [label = "Attack magic"];
>> n70146110697240 -> {n70146110695080; n70146110694480;};
>> n70146110695080 [label = "Summoned Beast X"];
>> n70146110694480 [label = "Summoned Beast Y"];
>> n70146110692060 [label = "Repel sword in length"];
>> n70146110691720 [label = "Defense"];
>> n70146110691220 [label = "Withdraw"];
>> n70146110691220 -> {n70146110690400; n70146110689620;};
>> n70146110690400 [label = "To stop"];
>> n70146110690400 -> {n70146110690220; n70146110689820;};
>> n70146110690220 [label = "Place a trap"];
>> n70146110689820 [label = "Shoot a bow and arrow"];
>> n70146110689620 [label = "To escape"];
>> n70146110689500 [label = "Break"];
>> n70146110689500 -> {n70146110688500; n70146110687660;};
>> n70146110688500 [label = "Stop"];
>> n70146110687660 [label = "Recover"];
>> n70146110687660 -> {n70146110686920; n70146110686220;};
>> n70146110686920 [label = "Recovery magic"];
>> n70146110686220 [label = "Drink recovery medicine"];
>> }
#+END_SRC
*** How can I check the image conversion immediately when debugging?
#+BEGIN_SRC ruby
TreeSupport.graph_open(root)
#+END_SRC
Equivalent to the next shortcut
#+BEGIN_SRC ruby
TreeSupport.graphviz(root).output("_output.png")
open _output.png
#+END_SRC
*** Troublesome making node classes yourself
You can use =TreeSupport::Node= as it is.
#+BEGIN_SRC ruby
TreeSupport::Node.new("root") do
add "Battle" do
add "Attack" do
add "Shake the sword"
add "Attack magic" do
add "Summoned Beast X"
add "Summoned Beast Y"
end
end
end
end
#+END_SRC
*** Troublesome making trees
#+BEGIN_SRC ruby
TreeSupport.example
#+END_SRC
There is a simple sample tree
*** How to trace leaves?
If you include =TreeSupport::Treeable= you can use each_node
#+BEGIN_SRC ruby
root = TreeSupport.example
root.each_node.with_index { |n, i| p [i, n.name] }
>> [0, "root"]
>> [1, "Battle"]
>> [2, "Attack"]
>> [3, "Shake the sword"]
>> [4, "Attack magic"]
>> [5, "Summoned Beast X"]
>> [6, "Summoned Beast Y"]
>> [7, "Repel sword in length"]
>> [8, "Defense"]
>> [9, "Withdraw"]
>> [10, "To stop"]
>> [11, "Place a trap"]
>> [12, "Shoot a bow and arrow"]
>> [13, "To escape"]
>> [14, "Break"]
>> [15, "Stop"]
>> [16, "Recover"]
>> [17, "Recovery magic"]
>> [18, "Drink recovery medicine"]
#+END_SRC
*** I do not want to display the root
#+BEGIN_SRC ruby
puts TreeSupport.tree(root, drop: 1)
>> Battle
>> ├─Attack
>> │ ├─Shake the sword
>> │ ├─Attack magic
>> │ │ ├─Summoned Beast X
>> │ │ └─Summoned Beast Y
>> │ └─Repel sword in length
>> └─Defense
>> Withdraw
>> ├─To stop
>> │ ├─Place a trap
>> │ └─Shoot a bow and arrow
>> └─To escape
>> Break
>> ├─Stop
>> └─Recover
>> ├─Recovery magic
>> └─Drink recovery medicine
#+END_SRC
*** Since the trees are too big, it is enough up to the depth 3
#+BEGIN_SRC ruby
puts TreeSupport.tree(root, take: 3)
>> root
>> ├─Battle
>> │ ├─Attack
>> │ └─Defense
>> ├─Withdraw
>> │ ├─To stop
>> │ └─To escape
>> └─Break
>> ├─Stop
>> └─Recover
#+END_SRC
*** When you combine both
#+BEGIN_SRC ruby
puts TreeSupport.tree(root, take: 3, drop: 1)
>> Battle
>> ├─Attack
>> └─Defense
>> Withdraw
>> ├─To stop
>> └─To escape
>> Break
>> ├─Stop
>> └─Recover
#+END_SRC
*** Image version also has similar options
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root, drop: 1)
gv.output("drop.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/drop.png]]
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root, take: 3)
gv.output("take.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/take.png]]
#+BEGIN_SRC ruby
gv = TreeSupport.graphviz(root, take: 3, drop: 1)
gv.output("take_drop.png")
#+END_SRC
[[https://raw.github.com/akicho8/tree_support/master/images/take_drop.png]]
*** Methods for easily making trees from data like CSV
Conversion from Array to Tree
#+BEGIN_SRC ruby
require "tree_support"
records = [
{key: :a, parent: nil},
{key: :b, parent: :a},
{key: :c, parent: :b},
]
When the first node is regarded as a parent
puts TreeSupport.records_to_tree(records).first.to_s_tree
>> a
>> └─b
>> └─c
When you make a parent parenting the whole
puts TreeSupport.records_to_tree(records, root_key: :root).to_s_tree
>> root
>> └─a
>> └─b
>> └─c
#+END_SRC
Conversion from Tree to Array
#+BEGIN_SRC ruby
tree = TreeSupport.records_to_tree(records)
pp TreeSupport.tree_to_records(tree.first)
>> [
>> {:key=>:a, :parent=>nil},
>> {:key=>:b, :parent=>:a},
>> {:key=>:c, :parent=>:b},
>> ]
#+END_SRC
*** How to use acts_as_tree equivalent?
Migration
#+BEGIN_SRC ruby
create_table :nodes do |t|
t.belongs_to :parent
end
#+END_SRC
Model
#+BEGIN_SRC ruby
class Node < ActiveRecord::Base
ar_tree_model
end
#+END_SRC
Difference from https://github.com/amerine/acts_as_tree
- simple
- Safely delete all safe_destroy_all (accident with destroy_all in combination with acts_as_list)
- Node.roots is defined by scope
- Arguments are different. =:order => :id= if you want to do it =scope: -> { order(:id) }=. By doing this you can also pass the where condition.
*** How do I correspond to memory_record gem?
Just as with ordinary classes, we need parent and children methods
#+BEGIN_SRC ruby
class TreeModel
include MemoryRecord
memory_record [
{key: :a, parent: nil},
{key: :b, parent: :a},
{key: :c, parent: :b},
]
include TreeSupport::Treeable
include TreeSupport::Stringify
def parent
self.class[super]
end
def children
self.class.find_all { |e| e.parent == self }
end
end
puts TreeModel.find_all(&:root?).collect(&:to_s_tree)
>> A
>> └─B
>> └─C
#+END_SRC
** With concern
- Since Gviz extends the standard class, concerns about future interference when combined with Rails (Active Support) etc.