arboreal 0.0.5 → 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.
- data/README.rdoc +16 -8
- data/lib/arboreal/class_methods.rb +2 -0
- data/lib/arboreal/instance_methods.rb +19 -9
- data/lib/arboreal/version.rb +1 -1
- data/spec/arboreal_spec.rb +40 -1
- data/spec/spec_helper.rb +1 -1
- metadata +43 -12
data/README.rdoc
CHANGED
@@ -2,35 +2,43 @@
|
|
2
2
|
|
3
3
|
Arboreal is yet another extension to ActiveRecord to support tree-shaped
|
4
4
|
data structures.
|
5
|
-
|
5
|
+
|
6
6
|
Arboreal surfaces relationships within the tree like +children+,
|
7
7
|
+ancestors+, +descendants+, and +siblings+ as scopes, so that additional
|
8
8
|
filtering/pagination can be performed.
|
9
|
+
|
10
|
+
It delegates as much work as possible to the underlying DBMS, making it efficient to:
|
11
|
+
|
12
|
+
* fetch all ancestors, descendants or siblings of a node
|
13
|
+
* move nodes (or subtrees) around
|
14
|
+
* prevent loops
|
15
|
+
* rebuild the hierarchy
|
9
16
|
|
10
17
|
== Getting started
|
11
18
|
|
12
19
|
First, install the "arboreal" gem, and add it to your Rails project's <tt>config/environment.rb</tt>.
|
13
20
|
|
14
|
-
Next, you'll need a migration to add
|
21
|
+
Next, you'll need a migration to add +parent_id+ and +ancestry_string+ columns, and indices:
|
15
22
|
|
16
23
|
class MakeThingsArboreal < ActiveRecord::Migration
|
17
24
|
|
18
25
|
def self.up
|
26
|
+
add_column "things", "parent_id", :integer
|
27
|
+
add_index "things", ["parent_id"]
|
19
28
|
add_column "things", "ancestry_string", :string
|
20
29
|
add_index "things", ["ancestry_string"]
|
21
|
-
Thing.rebuild_ancestry
|
22
30
|
end
|
23
31
|
|
24
32
|
def self.down
|
25
33
|
remove_index "things", ["ancestry_string"]
|
26
34
|
remove_column "things", "ancestry_string"
|
35
|
+
remove_index "things", ["parent_id"]
|
36
|
+
remove_column "things", "parent_id"
|
27
37
|
end
|
28
38
|
|
29
39
|
end
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
Finally, you can declare you model arboreal:
|
41
|
+
Finally, you can declare your model arboreal:
|
34
42
|
|
35
43
|
class Thing < ActiveRecord::Base
|
36
44
|
|
@@ -39,10 +47,10 @@ Finally, you can declare you model arboreal:
|
|
39
47
|
# .. etc etc ...
|
40
48
|
|
41
49
|
end
|
42
|
-
|
50
|
+
|
43
51
|
== Navigating the tree
|
44
52
|
|
45
|
-
Arboreal adds the relationships you'd expect:
|
53
|
+
Arboreal adds the basic relationships you'd expect:
|
46
54
|
|
47
55
|
* <tt>parent</tt>
|
48
56
|
* <tt>children</tt>
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require "active_support/core_ext/string/filters"
|
2
|
+
|
1
3
|
module Arboreal
|
2
4
|
module InstanceMethods
|
3
5
|
|
@@ -11,22 +13,22 @@ module Arboreal
|
|
11
13
|
|
12
14
|
# return a scope matching all ancestors of this node
|
13
15
|
def ancestors
|
14
|
-
|
16
|
+
model_base_class.scoped(:conditions => ancestor_conditions, :order => [:ancestry_string])
|
15
17
|
end
|
16
18
|
|
17
19
|
# return a scope matching all descendants of this node
|
18
20
|
def descendants
|
19
|
-
|
21
|
+
model_base_class.scoped(:conditions => descendant_conditions)
|
20
22
|
end
|
21
23
|
|
22
24
|
# return a scope matching all descendants of this node, AND the node itself
|
23
25
|
def subtree
|
24
|
-
|
26
|
+
model_base_class.scoped(:conditions => subtree_conditions)
|
25
27
|
end
|
26
28
|
|
27
29
|
# return a scope matching all siblings of this node (NOT including the node itself)
|
28
30
|
def siblings
|
29
|
-
|
31
|
+
model_base_class.scoped(:conditions => sibling_conditions)
|
30
32
|
end
|
31
33
|
|
32
34
|
# return the root of the tree
|
@@ -36,30 +38,38 @@ module Arboreal
|
|
36
38
|
|
37
39
|
private
|
38
40
|
|
41
|
+
def model_base_class
|
42
|
+
self.class.base_class
|
43
|
+
end
|
44
|
+
|
45
|
+
def table_name
|
46
|
+
self.class.table_name
|
47
|
+
end
|
48
|
+
|
39
49
|
def ancestor_conditions
|
40
50
|
["id in (?)", ancestor_ids]
|
41
51
|
end
|
42
52
|
|
43
53
|
def descendant_conditions
|
44
|
-
["#{
|
54
|
+
["#{table_name}.ancestry_string like ?", path_string + "%"]
|
45
55
|
end
|
46
56
|
|
47
57
|
def subtree_conditions
|
48
58
|
[
|
49
|
-
"#{
|
59
|
+
"#{table_name}.id = ? OR #{table_name}.ancestry_string like ?",
|
50
60
|
id, path_string + "%"
|
51
61
|
]
|
52
62
|
end
|
53
63
|
|
54
64
|
def sibling_conditions
|
55
65
|
[
|
56
|
-
"#{
|
66
|
+
"#{table_name}.id <> ? AND #{table_name}.parent_id = ?",
|
57
67
|
id, parent_id
|
58
68
|
]
|
59
69
|
end
|
60
70
|
|
61
71
|
def populate_ancestry_string
|
62
|
-
|
72
|
+
model_base_class.send(:with_exclusive_scope) do
|
63
73
|
self.ancestry_string = parent ? parent.path_string : "-"
|
64
74
|
end
|
65
75
|
end
|
@@ -86,7 +96,7 @@ module Arboreal
|
|
86
96
|
if @ancestry_change
|
87
97
|
old_ancestry_string, new_ancestry_string = *@ancestry_change
|
88
98
|
connection.update(<<-SQL.squish)
|
89
|
-
UPDATE #{
|
99
|
+
UPDATE #{table_name}
|
90
100
|
SET ancestry_string = REPLACE(ancestry_string, '#{old_ancestry_string}', '#{new_ancestry_string}')
|
91
101
|
WHERE ancestry_string LIKE '#{old_ancestry_string}%'
|
92
102
|
SQL
|
data/lib/arboreal/version.rb
CHANGED
data/spec/arboreal_spec.rb
CHANGED
@@ -11,6 +11,7 @@ class Node < ActiveRecord::Base
|
|
11
11
|
def self.up
|
12
12
|
create_table "nodes", :force => true do |t|
|
13
13
|
t.string "name"
|
14
|
+
t.string "type"
|
14
15
|
t.integer "parent_id"
|
15
16
|
t.string "ancestry_string"
|
16
17
|
end
|
@@ -24,7 +25,7 @@ class Node < ActiveRecord::Base
|
|
24
25
|
|
25
26
|
end
|
26
27
|
|
27
|
-
describe "
|
28
|
+
describe "Arboreal hierarchy" do
|
28
29
|
|
29
30
|
before(:all) do
|
30
31
|
Node::Migration.up
|
@@ -259,4 +260,42 @@ describe "{Arboreal}" do
|
|
259
260
|
|
260
261
|
end
|
261
262
|
|
263
|
+
class RedNode < Node; end
|
264
|
+
class GreenNode < Node; end
|
265
|
+
class BlueNode < Node; end
|
266
|
+
|
267
|
+
describe "polymorphic hierarchy" do
|
268
|
+
|
269
|
+
before(:all) do
|
270
|
+
Node::Migration.up
|
271
|
+
end
|
272
|
+
|
273
|
+
after(:all) do
|
274
|
+
Node::Migration.down
|
275
|
+
end
|
276
|
+
|
277
|
+
before do
|
278
|
+
@red = RedNode.create!
|
279
|
+
@green = GreenNode.create!(:parent => @red)
|
280
|
+
@blue = BlueNode.create!(:parent => @green)
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#descendants" do
|
284
|
+
it "includes nodes of other types" do
|
285
|
+
@red.descendants.should include(@green, @blue)
|
286
|
+
end
|
287
|
+
end
|
262
288
|
|
289
|
+
describe "#subtree" do
|
290
|
+
it "includes nodes of other types" do
|
291
|
+
@red.subtree.should include(@red, @green, @blue)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
describe "#ancestors" do
|
296
|
+
it "includes nodes of other types" do
|
297
|
+
@blue.ancestors.should include(@red, @green)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arboreal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Mike Williams
|
@@ -9,29 +14,51 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-18 00:00:00 +10:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: activerecord
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 2
|
29
|
+
- 3
|
30
|
+
- 0
|
31
|
+
version: 2.3.0
|
17
32
|
type: :runtime
|
18
|
-
|
19
|
-
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: activesupport
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
20
38
|
requirements:
|
21
39
|
- - ">="
|
22
40
|
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 2
|
43
|
+
- 3
|
44
|
+
- 0
|
23
45
|
version: 2.3.0
|
24
|
-
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
25
48
|
- !ruby/object:Gem::Dependency
|
26
49
|
name: rspec
|
27
|
-
|
28
|
-
|
29
|
-
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
30
52
|
requirements:
|
31
53
|
- - ">="
|
32
54
|
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 1
|
57
|
+
- 2
|
58
|
+
- 9
|
33
59
|
version: 1.2.9
|
34
|
-
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
35
62
|
description: |
|
36
63
|
Arboreal is yet another extension to ActiveRecord to support tree-shaped data structures.
|
37
64
|
|
@@ -76,18 +103,22 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
76
103
|
requirements:
|
77
104
|
- - ">="
|
78
105
|
- !ruby/object:Gem::Version
|
106
|
+
segments:
|
107
|
+
- 1
|
108
|
+
- 8
|
109
|
+
- 7
|
79
110
|
version: 1.8.7
|
80
|
-
version:
|
81
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
112
|
requirements:
|
83
113
|
- - ">="
|
84
114
|
- !ruby/object:Gem::Version
|
115
|
+
segments:
|
116
|
+
- 0
|
85
117
|
version: "0"
|
86
|
-
version:
|
87
118
|
requirements: []
|
88
119
|
|
89
120
|
rubyforge_project:
|
90
|
-
rubygems_version: 1.3.
|
121
|
+
rubygems_version: 1.3.6
|
91
122
|
signing_key:
|
92
123
|
specification_version: 3
|
93
124
|
summary: Efficient tree structures for ActiveRecord
|