arboreal 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|