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.
@@ -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