Install as a standalone gem
$ gem install tree_support
Or install within application using Gemfile
$ bundle add tree_support
$ bundle install
Just pass the object that has the parent, children method to TreeSupport.tree
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
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
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
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
include TreeSupport::Stringify
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
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
Add a block to tree
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
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
gv = TreeSupport.graphviz(root)
gv.output("tree.png")
Return the graphviz attribute as a hash in TreeSupport.graphviz block
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")
As with the above method, it returns a hash containing the label value
gv = TreeSupport.graphviz(root) do |node|
{label: node.name.chars.first}
end
gv.output("tree_label.png")
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"];
# >> }
TreeSupport.graph_open(root)
Equivalent to the next shortcut
TreeSupport.graphviz(root).output("_output.png")
`open _output.png`
You can use TreeSupport::Node
as it is.
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
TreeSupport.example
There is a simple sample tree
If you include TreeSupport::Treeable
you can use each_node
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"]
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
puts TreeSupport.tree(root, take: 3)
# >> *root*
# >> ├─Battle
# >> │ ├─Attack
# >> │ └─Defense
# >> ├─Withdraw
# >> │ ├─To stop
# >> │ └─To escape
# >> └─Break
# >> ├─Stop
# >> └─Recover
puts TreeSupport.tree(root, take: 3, drop: 1)
# >> Battle
# >> ├─Attack
# >> └─Defense
# >> Withdraw
# >> ├─To stop
# >> └─To escape
# >> Break
# >> ├─Stop
# >> └─Recover
gv = TreeSupport.graphviz(root, drop: 1)
gv.output("drop.png")
gv = TreeSupport.graphviz(root, take: 3)
gv.output("take.png")
gv = TreeSupport.graphviz(root, take: 3, drop: 1)
gv.output("take_drop.png")
Conversion from Array to Tree
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
Conversion from Tree to Array
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},
# >> ]
Migration
create_table :nodes do |t|
t.belongs_to :parent
end
Model
class Node < ActiveRecord::Base
ar_tree_model
end
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 itscope: -> { order(:id) }
. By doing this you can also pass the where condition.
Just as with ordinary classes, we need parent and children methods
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
- Since Gviz extends the standard class, concerns about future interference when combined with Rails (Active Support) etc.