tree_support 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/README.org +471 -0
- data/Rakefile +6 -0
- data/examples/0100_simple.rb +22 -0
- data/examples/0110_embeded_node_class.rb +26 -0
- data/examples/0120_graphiz_output_image.rb +6 -0
- data/examples/0130_node_class_example.rb +98 -0
- data/examples/0140_active_record.rb +81 -0
- data/examples/0150_acts_as_tree.rb +101 -0
- data/examples/0160_acts_as_tree_and_list.rb +133 -0
- data/examples/0170_node_class.rb +28 -0
- data/examples/0180_replace_to_active_record_tree.rb +120 -0
- data/examples/0190_generate_ruby_code.rb +68 -0
- data/examples/0200_ar_tree_model.rb +82 -0
- data/examples/0201_safe_destroy_all.rb +55 -0
- data/examples/0210_take_drop.rb +40 -0
- data/examples/0220_it_will_not_be_strange_to_tojson.rb +27 -0
- data/examples/0230_list_to_tree.rb +68 -0
- data/examples/0240_memory_record.rb +34 -0
- data/examples/Gemfile +12 -0
- data/examples/Gemfile.lock +137 -0
- data/examples/demo.rb +126 -0
- data/images/drop.png +0 -0
- data/images/take.png +0 -0
- data/images/take_drop.png +0 -0
- data/images/tree.png +0 -0
- data/images/tree_color.png +0 -0
- data/images/tree_label.png +0 -0
- data/lib/tree_support/ar_tree_model.rb +74 -0
- data/lib/tree_support/graphviz_builder.rb +78 -0
- data/lib/tree_support/inspector.rb +116 -0
- data/lib/tree_support/node.rb +124 -0
- data/lib/tree_support/railtie.rb +9 -0
- data/lib/tree_support/tree_support.rb +28 -0
- data/lib/tree_support/treeable.rb +52 -0
- data/lib/tree_support/version.rb +3 -0
- data/lib/tree_support.rb +2 -0
- data/spec/ar_tree_model_spec.rb +82 -0
- data/spec/node_spec.rb +52 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/tree_support_spec.rb +28 -0
- data/spec/treeable_spec.rb +59 -0
- data/tree_support.gemspec +30 -0
- metadata +196 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "bundler/setup"
|
3
|
+
require "tree_support"
|
4
|
+
puts "--------------------------------------------------------------------------------"
|
5
|
+
puts TreeSupport.example.to_s_tree(drop: 3)
|
6
|
+
puts "--------------------------------------------------------------------------------"
|
7
|
+
puts TreeSupport.example.to_s_tree(take: 3)
|
8
|
+
puts "--------------------------------------------------------------------------------"
|
9
|
+
puts TreeSupport.example.to_s_tree(take: 3, drop: 1)
|
10
|
+
# >> --------------------------------------------------------------------------------
|
11
|
+
# >> Shake the sword
|
12
|
+
# >> Attack magic
|
13
|
+
# >> ├─Summoned Beast X
|
14
|
+
# >> └─Summoned Beast Y
|
15
|
+
# >> Repel sword in length
|
16
|
+
# >> Place a trap
|
17
|
+
# >> Shoot a bow and arrow
|
18
|
+
# >> Recovery magic
|
19
|
+
# >> Drink recovery medicine
|
20
|
+
# >> --------------------------------------------------------------------------------
|
21
|
+
# >> *root*
|
22
|
+
# >> ├─Battle
|
23
|
+
# >> │ ├─Attack
|
24
|
+
# >> │ └─Defense
|
25
|
+
# >> ├─Withdraw
|
26
|
+
# >> │ ├─To stop
|
27
|
+
# >> │ └─To escape
|
28
|
+
# >> └─Break
|
29
|
+
# >> ├─Stop
|
30
|
+
# >> └─Recover
|
31
|
+
# >> --------------------------------------------------------------------------------
|
32
|
+
# >> Battle
|
33
|
+
# >> ├─Attack
|
34
|
+
# >> └─Defense
|
35
|
+
# >> Withdraw
|
36
|
+
# >> ├─To stop
|
37
|
+
# >> └─To escape
|
38
|
+
# >> Break
|
39
|
+
# >> ├─Stop
|
40
|
+
# >> └─Recover
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# It will not be strange to tojson
|
2
|
+
#
|
3
|
+
require "bundler/setup"
|
4
|
+
|
5
|
+
require "rails"
|
6
|
+
require "active_record"
|
7
|
+
require "tree_support"
|
8
|
+
require "byebug"
|
9
|
+
|
10
|
+
Class.new(Rails::Application){config.eager_load = false}.initialize! # In order to read Railtie
|
11
|
+
|
12
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
|
13
|
+
ActiveRecord::Migration.verbose = false
|
14
|
+
|
15
|
+
ActiveRecord::Schema.define do
|
16
|
+
create_table :nodes do |t|
|
17
|
+
t.belongs_to :parent
|
18
|
+
t.string :name
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Node < ActiveRecord::Base
|
23
|
+
ar_tree_model
|
24
|
+
end
|
25
|
+
|
26
|
+
Node.create!(name: "*root*")
|
27
|
+
Node.first.to_json # => "{\"id\":1,\"parent_id\":null,\"name\":\"*root*\"}"
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "tree_support"
|
3
|
+
|
4
|
+
records = [
|
5
|
+
{key: :a0, parent: nil},
|
6
|
+
{key: :a1, parent: :a0},
|
7
|
+
{key: :a2, parent: :a1},
|
8
|
+
|
9
|
+
{key: :b0, parent: nil},
|
10
|
+
{key: :b1, parent: :b0},
|
11
|
+
{key: :b2, parent: :b1},
|
12
|
+
]
|
13
|
+
|
14
|
+
# ---------- Convert with function of library
|
15
|
+
if true
|
16
|
+
puts TreeSupport.records_to_tree(records).collect(&:to_s_tree)
|
17
|
+
puts TreeSupport.records_to_tree(records, root_key: :root).to_s_tree
|
18
|
+
end
|
19
|
+
|
20
|
+
# ---------- How to write by yourself
|
21
|
+
if true
|
22
|
+
source_hash = records.inject({}) { |a, e| a.merge(e[:key] => e) }
|
23
|
+
node_hash = records.inject({}) { |a, e| a.merge(e[:key] => TreeSupport::Node.new(e[:key])) }
|
24
|
+
node_hash.each_value { |node|
|
25
|
+
if parent = source_hash[node.key][:parent]
|
26
|
+
parent_node = node_hash[parent]
|
27
|
+
node.parent = parent_node
|
28
|
+
parent_node.children << node
|
29
|
+
end
|
30
|
+
}
|
31
|
+
roots = node_hash.each_value.find_all { |e| e.parent == nil }
|
32
|
+
puts roots.collect(&:to_s_tree)
|
33
|
+
|
34
|
+
# When you want to make one route
|
35
|
+
root = TreeSupport::Node.new(:root)
|
36
|
+
roots.each do |e|
|
37
|
+
e.parent = root
|
38
|
+
root.children << e
|
39
|
+
end
|
40
|
+
puts root.to_s_tree
|
41
|
+
end
|
42
|
+
|
43
|
+
# >> a0
|
44
|
+
# >> └─a1
|
45
|
+
# >> └─a2
|
46
|
+
# >> b0
|
47
|
+
# >> └─b1
|
48
|
+
# >> └─b2
|
49
|
+
# >> root
|
50
|
+
# >> ├─a0
|
51
|
+
# >> │ └─a1
|
52
|
+
# >> │ └─a2
|
53
|
+
# >> └─b0
|
54
|
+
# >> └─b1
|
55
|
+
# >> └─b2
|
56
|
+
# >> a0
|
57
|
+
# >> └─a1
|
58
|
+
# >> └─a2
|
59
|
+
# >> b0
|
60
|
+
# >> └─b1
|
61
|
+
# >> └─b2
|
62
|
+
# >> root
|
63
|
+
# >> ├─a0
|
64
|
+
# >> │ └─a1
|
65
|
+
# >> │ └─a2
|
66
|
+
# >> └─b0
|
67
|
+
# >> └─b1
|
68
|
+
# >> └─b2
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# Example of combining with MemoryRecord
|
2
|
+
require "bundler/setup"
|
3
|
+
require "tree_support"
|
4
|
+
require "memory_record"
|
5
|
+
|
6
|
+
class Foo
|
7
|
+
include MemoryRecord
|
8
|
+
memory_record [
|
9
|
+
{key: :a, name: "A", parent: nil},
|
10
|
+
{key: :b, name: "B", parent: :a},
|
11
|
+
{key: :c, name: "C", parent: :b},
|
12
|
+
]
|
13
|
+
|
14
|
+
# Any structure can be used as long as it can respond to parent and children
|
15
|
+
concerning :TreeMethods do
|
16
|
+
included do
|
17
|
+
include TreeSupport::Treeable
|
18
|
+
include TreeSupport::Stringify
|
19
|
+
end
|
20
|
+
|
21
|
+
def parent
|
22
|
+
self.class[super]
|
23
|
+
end
|
24
|
+
|
25
|
+
def children
|
26
|
+
self.class.find_all {|e| e.parent == self }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
puts Foo.find_all(&:root?).collect(&:to_s_tree)
|
32
|
+
# >> A
|
33
|
+
# >> └─B
|
34
|
+
# >> └─C
|
data/examples/Gemfile
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ..
|
3
|
+
specs:
|
4
|
+
tree_support (0.0.1)
|
5
|
+
activesupport
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actioncable (5.0.0)
|
11
|
+
actionpack (= 5.0.0)
|
12
|
+
nio4r (~> 1.2)
|
13
|
+
websocket-driver (~> 0.6.1)
|
14
|
+
actionmailer (5.0.0)
|
15
|
+
actionpack (= 5.0.0)
|
16
|
+
actionview (= 5.0.0)
|
17
|
+
activejob (= 5.0.0)
|
18
|
+
mail (~> 2.5, >= 2.5.4)
|
19
|
+
rails-dom-testing (~> 2.0)
|
20
|
+
actionpack (5.0.0)
|
21
|
+
actionview (= 5.0.0)
|
22
|
+
activesupport (= 5.0.0)
|
23
|
+
rack (~> 2.0)
|
24
|
+
rack-test (~> 0.6.3)
|
25
|
+
rails-dom-testing (~> 2.0)
|
26
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
27
|
+
actionview (5.0.0)
|
28
|
+
activesupport (= 5.0.0)
|
29
|
+
builder (~> 3.1)
|
30
|
+
erubis (~> 2.7.0)
|
31
|
+
rails-dom-testing (~> 2.0)
|
32
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
33
|
+
activejob (5.0.0)
|
34
|
+
activesupport (= 5.0.0)
|
35
|
+
globalid (>= 0.3.6)
|
36
|
+
activemodel (5.0.0)
|
37
|
+
activesupport (= 5.0.0)
|
38
|
+
activerecord (5.0.0)
|
39
|
+
activemodel (= 5.0.0)
|
40
|
+
activesupport (= 5.0.0)
|
41
|
+
arel (~> 7.0)
|
42
|
+
activesupport (5.0.0)
|
43
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
44
|
+
i18n (~> 0.7)
|
45
|
+
minitest (~> 5.1)
|
46
|
+
tzinfo (~> 1.1)
|
47
|
+
acts_as_list (0.7.6)
|
48
|
+
activerecord (>= 3.0)
|
49
|
+
acts_as_tree (2.4.0)
|
50
|
+
activerecord (>= 3.0.0)
|
51
|
+
arel (7.1.0)
|
52
|
+
builder (3.2.2)
|
53
|
+
byebug (9.0.5)
|
54
|
+
concurrent-ruby (1.0.2)
|
55
|
+
erubis (2.7.0)
|
56
|
+
globalid (0.3.6)
|
57
|
+
activesupport (>= 4.1.0)
|
58
|
+
gviz (0.3.5)
|
59
|
+
thor
|
60
|
+
i18n (0.7.0)
|
61
|
+
loofah (2.0.3)
|
62
|
+
nokogiri (>= 1.5.9)
|
63
|
+
mail (2.6.4)
|
64
|
+
mime-types (>= 1.16, < 4)
|
65
|
+
memory_record (0.0.6)
|
66
|
+
activemodel
|
67
|
+
activesupport
|
68
|
+
method_source (0.8.2)
|
69
|
+
mime-types (3.1)
|
70
|
+
mime-types-data (~> 3.2015)
|
71
|
+
mime-types-data (3.2016.0521)
|
72
|
+
mini_portile2 (2.1.0)
|
73
|
+
minitest (5.9.0)
|
74
|
+
nio4r (1.2.1)
|
75
|
+
nokogiri (1.6.8)
|
76
|
+
mini_portile2 (~> 2.1.0)
|
77
|
+
pkg-config (~> 1.1.7)
|
78
|
+
pkg-config (1.1.7)
|
79
|
+
rack (2.0.1)
|
80
|
+
rack-test (0.6.3)
|
81
|
+
rack (>= 1.0)
|
82
|
+
rails (5.0.0)
|
83
|
+
actioncable (= 5.0.0)
|
84
|
+
actionmailer (= 5.0.0)
|
85
|
+
actionpack (= 5.0.0)
|
86
|
+
actionview (= 5.0.0)
|
87
|
+
activejob (= 5.0.0)
|
88
|
+
activemodel (= 5.0.0)
|
89
|
+
activerecord (= 5.0.0)
|
90
|
+
activesupport (= 5.0.0)
|
91
|
+
bundler (>= 1.3.0, < 2.0)
|
92
|
+
railties (= 5.0.0)
|
93
|
+
sprockets-rails (>= 2.0.0)
|
94
|
+
rails-dom-testing (2.0.1)
|
95
|
+
activesupport (>= 4.2.0, < 6.0)
|
96
|
+
nokogiri (~> 1.6.0)
|
97
|
+
rails-html-sanitizer (1.0.3)
|
98
|
+
loofah (~> 2.0)
|
99
|
+
railties (5.0.0)
|
100
|
+
actionpack (= 5.0.0)
|
101
|
+
activesupport (= 5.0.0)
|
102
|
+
method_source
|
103
|
+
rake (>= 0.8.7)
|
104
|
+
thor (>= 0.18.1, < 2.0)
|
105
|
+
rake (11.2.2)
|
106
|
+
sprockets (3.7.0)
|
107
|
+
concurrent-ruby (~> 1.0)
|
108
|
+
rack (> 1, < 3)
|
109
|
+
sprockets-rails (3.1.1)
|
110
|
+
actionpack (>= 4.0)
|
111
|
+
activesupport (>= 4.0)
|
112
|
+
sprockets (>= 3.0.0)
|
113
|
+
sqlite3 (1.3.11)
|
114
|
+
thor (0.19.1)
|
115
|
+
thread_safe (0.3.5)
|
116
|
+
tzinfo (1.2.2)
|
117
|
+
thread_safe (~> 0.1)
|
118
|
+
websocket-driver (0.6.4)
|
119
|
+
websocket-extensions (>= 0.1.0)
|
120
|
+
websocket-extensions (0.1.2)
|
121
|
+
|
122
|
+
PLATFORMS
|
123
|
+
ruby
|
124
|
+
|
125
|
+
DEPENDENCIES
|
126
|
+
activerecord
|
127
|
+
acts_as_list
|
128
|
+
acts_as_tree
|
129
|
+
byebug
|
130
|
+
gviz
|
131
|
+
memory_record
|
132
|
+
rails
|
133
|
+
sqlite3
|
134
|
+
tree_support!
|
135
|
+
|
136
|
+
BUNDLED WITH
|
137
|
+
1.16.0.pre.3
|
data/examples/demo.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
# output ../images/*
|
2
|
+
require "bundler/setup"
|
3
|
+
require "tree_support"
|
4
|
+
|
5
|
+
root = TreeSupport.example
|
6
|
+
|
7
|
+
p root.each_node.collect.with_index {|n, i| [n.name, i]}
|
8
|
+
puts root.to_s_tree
|
9
|
+
puts TreeSupport.tree(root)
|
10
|
+
puts TreeSupport.tree(root, drop: 1)
|
11
|
+
puts TreeSupport.tree(root, take: 3)
|
12
|
+
puts TreeSupport.tree(root, take: 3, drop: 1)
|
13
|
+
puts TreeSupport.tree(root) {|node, _locals| node.object_id}
|
14
|
+
# TreeSupport.graph_open(root)
|
15
|
+
|
16
|
+
TreeSupport.graphviz(root) {|node|
|
17
|
+
if node.name.include?("Attack")
|
18
|
+
{fillcolor: "lightblue", style: "filled"}
|
19
|
+
elsif node.name.include?("Recover")
|
20
|
+
{fillcolor: "lightpink", style: "filled"}
|
21
|
+
end
|
22
|
+
}.output("../images/tree_color.png")
|
23
|
+
|
24
|
+
TreeSupport.graphviz(root) {|node|
|
25
|
+
{label: node.name.chars.first}
|
26
|
+
}.output("../images/tree_label.png")
|
27
|
+
|
28
|
+
TreeSupport.graphviz(root).output("../images/tree.png")
|
29
|
+
TreeSupport.graphviz(root, take: 3).output("../images/take.png")
|
30
|
+
TreeSupport.graphviz(root, drop: 1).output("../images/drop.png")
|
31
|
+
TreeSupport.graphviz(root, take: 3, drop: 1).output("../images/take_drop.png")
|
32
|
+
# >> [["*root*", 0], ["Battle", 1], ["Attack", 2], ["Shake the sword", 3], ["Attack magic", 4], ["Summoned Beast X", 5], ["Summoned Beast Y", 6], ["Repel sword in length", 7], ["Defense", 8], ["Withdraw", 9], ["To stop", 10], ["Place a trap", 11], ["Shoot a bow and arrow", 12], ["To escape", 13], ["Break", 14], ["Stop", 15], ["Recover", 16], ["Recovery magic", 17], ["Drink recovery medicine", 18]]
|
33
|
+
# >> *root*
|
34
|
+
# >> ├─Battle
|
35
|
+
# >> │ ├─Attack
|
36
|
+
# >> │ │ ├─Shake the sword
|
37
|
+
# >> │ │ ├─Attack magic
|
38
|
+
# >> │ │ │ ├─Summoned Beast X
|
39
|
+
# >> │ │ │ └─Summoned Beast Y
|
40
|
+
# >> │ │ └─Repel sword in length
|
41
|
+
# >> │ └─Defense
|
42
|
+
# >> ├─Withdraw
|
43
|
+
# >> │ ├─To stop
|
44
|
+
# >> │ │ ├─Place a trap
|
45
|
+
# >> │ │ └─Shoot a bow and arrow
|
46
|
+
# >> │ └─To escape
|
47
|
+
# >> └─Break
|
48
|
+
# >> ├─Stop
|
49
|
+
# >> └─Recover
|
50
|
+
# >> ├─Recovery magic
|
51
|
+
# >> └─Drink recovery medicine
|
52
|
+
# >> *root*
|
53
|
+
# >> ├─Battle
|
54
|
+
# >> │ ├─Attack
|
55
|
+
# >> │ │ ├─Shake the sword
|
56
|
+
# >> │ │ ├─Attack magic
|
57
|
+
# >> │ │ │ ├─Summoned Beast X
|
58
|
+
# >> │ │ │ └─Summoned Beast Y
|
59
|
+
# >> │ │ └─Repel sword in length
|
60
|
+
# >> │ └─Defense
|
61
|
+
# >> ├─Withdraw
|
62
|
+
# >> │ ├─To stop
|
63
|
+
# >> │ │ ├─Place a trap
|
64
|
+
# >> │ │ └─Shoot a bow and arrow
|
65
|
+
# >> │ └─To escape
|
66
|
+
# >> └─Break
|
67
|
+
# >> ├─Stop
|
68
|
+
# >> └─Recover
|
69
|
+
# >> ├─Recovery magic
|
70
|
+
# >> └─Drink recovery medicine
|
71
|
+
# >> Battle
|
72
|
+
# >> ├─Attack
|
73
|
+
# >> │ ├─Shake the sword
|
74
|
+
# >> │ ├─Attack magic
|
75
|
+
# >> │ │ ├─Summoned Beast X
|
76
|
+
# >> │ │ └─Summoned Beast Y
|
77
|
+
# >> │ └─Repel sword in length
|
78
|
+
# >> └─Defense
|
79
|
+
# >> Withdraw
|
80
|
+
# >> ├─To stop
|
81
|
+
# >> │ ├─Place a trap
|
82
|
+
# >> │ └─Shoot a bow and arrow
|
83
|
+
# >> └─To escape
|
84
|
+
# >> Break
|
85
|
+
# >> ├─Stop
|
86
|
+
# >> └─Recover
|
87
|
+
# >> ├─Recovery magic
|
88
|
+
# >> └─Drink recovery medicine
|
89
|
+
# >> *root*
|
90
|
+
# >> ├─Battle
|
91
|
+
# >> │ ├─Attack
|
92
|
+
# >> │ └─Defense
|
93
|
+
# >> ├─Withdraw
|
94
|
+
# >> │ ├─To stop
|
95
|
+
# >> │ └─To escape
|
96
|
+
# >> └─Break
|
97
|
+
# >> ├─Stop
|
98
|
+
# >> └─Recover
|
99
|
+
# >> Battle
|
100
|
+
# >> ├─Attack
|
101
|
+
# >> └─Defense
|
102
|
+
# >> Withdraw
|
103
|
+
# >> ├─To stop
|
104
|
+
# >> └─To escape
|
105
|
+
# >> Break
|
106
|
+
# >> ├─Stop
|
107
|
+
# >> └─Recover
|
108
|
+
# >> 70279213508120
|
109
|
+
# >> ├─70279213507760
|
110
|
+
# >> │ ├─70279213507500
|
111
|
+
# >> │ │ ├─70279213507240
|
112
|
+
# >> │ │ ├─70279213507000
|
113
|
+
# >> │ │ │ ├─70279213506800
|
114
|
+
# >> │ │ │ └─70279213506660
|
115
|
+
# >> │ │ └─70279213507200
|
116
|
+
# >> │ └─70279213509960
|
117
|
+
# >> ├─70279213498160
|
118
|
+
# >> │ ├─70279213497920
|
119
|
+
# >> │ │ ├─70279213497740
|
120
|
+
# >> │ │ └─70279213497600
|
121
|
+
# >> │ └─70279213497380
|
122
|
+
# >> └─70279213497180
|
123
|
+
# >> ├─70279213496960
|
124
|
+
# >> └─70279213496720
|
125
|
+
# >> ├─70279213496480
|
126
|
+
# >> └─70279213496320
|
data/images/drop.png
ADDED
Binary file
|
data/images/take.png
ADDED
Binary file
|
Binary file
|
data/images/tree.png
ADDED
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# acts_as_tree replacement
|
2
|
+
#
|
3
|
+
# class Node < ActiveRecord::Base
|
4
|
+
# ar_tree_model # default
|
5
|
+
# ar_tree_model scope: -> { order(:name) } # Designate the scope yourself
|
6
|
+
# ar_tree_model scope: -> { order(:id).where(active: true) } # You can also specify where
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
require "active_support/concern"
|
10
|
+
|
11
|
+
module TreeSupport
|
12
|
+
module ArTreeModel
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
end
|
17
|
+
|
18
|
+
class_methods do
|
19
|
+
def ar_tree_model(options = {})
|
20
|
+
return if ar_tree_model_defined?
|
21
|
+
|
22
|
+
class_attribute :ar_tree_model_configuration
|
23
|
+
self.ar_tree_model_configuration = {
|
24
|
+
scope: -> { order(:id) },
|
25
|
+
}.merge(options)
|
26
|
+
|
27
|
+
if block_given?
|
28
|
+
yield ar_tree_model_configuration
|
29
|
+
end
|
30
|
+
|
31
|
+
include SingletonMethods
|
32
|
+
end
|
33
|
+
|
34
|
+
def ar_tree_model_defined?
|
35
|
+
ancestors.include?(SingletonMethods)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
concern :SingletonMethods do
|
40
|
+
include Treeable
|
41
|
+
include Stringify
|
42
|
+
|
43
|
+
included do
|
44
|
+
scope :tree_default_scope, ar_tree_model_configuration[:scope]
|
45
|
+
|
46
|
+
belongs_to :parent, -> { tree_default_scope }, class_name: name, foreign_key: :parent_id, required: false
|
47
|
+
has_many :children, -> { tree_default_scope }, class_name: name, foreign_key: :parent_id, dependent: :destroy, inverse_of: :parent
|
48
|
+
scope :roots, -> { tree_default_scope.where(parent_id: nil) }
|
49
|
+
end
|
50
|
+
|
51
|
+
class_methods do
|
52
|
+
def ar_tree_model?
|
53
|
+
ar_tree_model_defined? # && columns_hash.has_key?(:id)
|
54
|
+
end
|
55
|
+
|
56
|
+
def root
|
57
|
+
roots.first
|
58
|
+
end
|
59
|
+
|
60
|
+
# In combination with acts_as_list accident in destroy_all, we have to erase from the leaves in order
|
61
|
+
def safe_destroy_all
|
62
|
+
roots.collect(&:destroy)
|
63
|
+
end
|
64
|
+
|
65
|
+
def destroy_all(*args)
|
66
|
+
if respond_to?(:acts_as_list)
|
67
|
+
ActiveSupport::Deprecation.warn("When you use acts_as_list destroy_all do not get id failed to accidentally delete_all Please use safe_destroy_all to erase from the end")
|
68
|
+
end
|
69
|
+
super
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "gviz"
|
2
|
+
|
3
|
+
# for GvizEx#output
|
4
|
+
require "pathname"
|
5
|
+
require "delegate"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
require "active_support/core_ext/module/delegation"
|
9
|
+
|
10
|
+
module TreeSupport
|
11
|
+
def self.graphviz(*args, &block)
|
12
|
+
GraphvizBuilder.build(*args, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.graph_open(*args, &block)
|
16
|
+
graphviz(*args, &block).output("_output.png")
|
17
|
+
`open _output.png`
|
18
|
+
end
|
19
|
+
|
20
|
+
class GraphvizBuilder
|
21
|
+
# For Gviz#save only for some reason
|
22
|
+
class GvizEx < SimpleDelegator
|
23
|
+
def output(filename)
|
24
|
+
filename = Pathname(filename).expand_path
|
25
|
+
FileUtils.makedirs(filename.dirname)
|
26
|
+
save("#{filename.dirname}/#{filename.basename(".*")}", filename.extname.delete("."))
|
27
|
+
end
|
28
|
+
|
29
|
+
# alias to_dot to_s can not
|
30
|
+
def to_dot
|
31
|
+
to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.build(object, *args, &block)
|
36
|
+
new(*args, &block).build(object)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(**options, &block)
|
40
|
+
@options = {
|
41
|
+
take: 256,
|
42
|
+
drop: 0,
|
43
|
+
}.merge(options)
|
44
|
+
@block = block
|
45
|
+
end
|
46
|
+
|
47
|
+
def build(object)
|
48
|
+
GvizEx.new(Gviz.new).tap do |gv|
|
49
|
+
gv.global(rankdir: "LR", concentrate: "true")
|
50
|
+
visit(gv, object)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def visit(gv, object, depth = 0)
|
57
|
+
if depth < @options[:take]
|
58
|
+
if @options[:drop] <= depth
|
59
|
+
attrs = {}
|
60
|
+
if @block
|
61
|
+
attrs = @block.call(object) || {}
|
62
|
+
end
|
63
|
+
attrs[:label] ||= TreeSupport.node_name(object)
|
64
|
+
gv.node(node_code(object), attrs)
|
65
|
+
if depth.next < @options[:take]
|
66
|
+
gv.route node_code(object) => object.children.collect {|node| node_code(node) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
object.children.each {|e| visit(gv, e, depth.next) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def node_code(object)
|
74
|
+
# I do not want to be a symbol because it is not subject to GC, but because I get angry with Gviz it is a symbolic symbol
|
75
|
+
:"n#{object.object_id}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|