arboreal 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 an +ancestry_string+ column, and index thereupon:
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
- This assumes that the table concerned already has a +parent_id+ column; if not, add that too (type +integer+, with a supporting index).
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 ClassMethods
3
5
 
@@ -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
- self.class.scoped(:conditions => ancestor_conditions, :order => [:ancestry_string])
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
- self.class.scoped(:conditions => descendant_conditions)
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
- self.class.scoped(:conditions => subtree_conditions)
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
- self.class.scoped(:conditions => sibling_conditions)
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
- ["#{self.class.table_name}.ancestry_string like ?", path_string + "%"]
54
+ ["#{table_name}.ancestry_string like ?", path_string + "%"]
45
55
  end
46
56
 
47
57
  def subtree_conditions
48
58
  [
49
- "#{self.class.table_name}.id = ? OR #{self.class.table_name}.ancestry_string like ?",
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
- "#{self.class.table_name}.id <> ? AND #{self.class.table_name}.parent_id = ?",
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
- self.class.send(:with_exclusive_scope) do
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 #{self.class.table_name}
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
@@ -1,3 +1,3 @@
1
1
  module Arboreal
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -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 "{Arboreal}" do
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
@@ -41,7 +41,7 @@ DB_CONFIGS = {
41
41
  }
42
42
  }
43
43
 
44
- test_adapter = (ENV["ARBOREAL_TEST_ADAPTER"] || "sqlite3")
44
+ test_adapter = (ENV["AR_ADAPTER"] || "sqlite3")
45
45
  test_db_config = DB_CONFIGS[test_adapter].merge(:adapter => test_adapter)
46
46
 
47
47
  Spec::Runner.configure do |config|
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: arboreal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
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-06 00:00:00 +10:00
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
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
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
- version:
46
+ type: :runtime
47
+ version_requirements: *id002
25
48
  - !ruby/object:Gem::Dependency
26
49
  name: rspec
27
- type: :development
28
- version_requirement:
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
- version:
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.5
121
+ rubygems_version: 1.3.6
91
122
  signing_key:
92
123
  specification_version: 3
93
124
  summary: Efficient tree structures for ActiveRecord