jnicklas-even_better_nested_set 0.3.2

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Jonas Nicklas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ EvenBetterNestedSet
2
+ ===================
3
+
4
+
5
+ this is an alternative to ActsAsNestedSet, and BetterNestedSet which is just a little bit less stupid.
6
+
7
+ class Directory < ActiveRecord::Base
8
+
9
+ acts_as_nested_set
10
+
11
+ end
12
+
13
+ d = Directory.new
14
+
15
+ d.children.create!(:name => 'blah')
16
+ d.children.create!(:name => 'gurr')
17
+ d.children.create!(:name => 'doh')
18
+
19
+ d.bounds #=> 1..8
20
+ d.children[1].bounds #=> 4..5
21
+ d.children[1].name #=> 'gurr'
22
+ d.children[1].parent #=> d
23
+
24
+ c = Directory.create!(:name => 'test', :parent => d.directory[1]
25
+
data/Rakefile ADDED
@@ -0,0 +1,87 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+
7
+ GEM = "eb_nested_set"
8
+ GEM_VERSION = "0.3.2"
9
+ AUTHOR = "Jonas Nicklas"
10
+ EMAIL = "jonas.nicklas@gmail.com"
11
+ HOMEPAGE = "http://github.com/jnicklas/even_better_nested_set/tree/master"
12
+ SUMMARY = "A cool acts_as_nested_set alternative"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = GEM
16
+ s.version = GEM_VERSION
17
+ s.platform = Gem::Platform::RUBY
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ['README.md', 'LICENSE']
20
+ s.summary = SUMMARY
21
+ s.description = s.summary
22
+ s.author = AUTHOR
23
+ s.email = EMAIL
24
+ s.homepage = HOMEPAGE
25
+ s.require_path = 'lib'
26
+ s.autorequire = GEM
27
+ s.files = %w(LICENSE README.md Rakefile init.rb) + Dir.glob("{lib,spec}/**/*")
28
+ end
29
+
30
+ Rake::GemPackageTask.new(spec) do |pkg|
31
+ pkg.gem_spec = spec
32
+ end
33
+
34
+ desc "install the plugin locally"
35
+ task :install => [:package] do
36
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} --no-update-sources}
37
+ end
38
+
39
+ desc "create a gemspec file"
40
+ task :make_spec do
41
+ File.open("#{GEM}.gemspec", "w") do |file|
42
+ file.puts spec.to_ruby
43
+ end
44
+ end
45
+
46
+ namespace :jruby do
47
+
48
+ desc "Run :package and install the resulting .gem with jruby"
49
+ task :install => :package do
50
+ sh %{#{SUDO} jruby -S gem install pkg/#{GEM}-#{GEM_VERSION}.gem --no-rdoc --no-ri}
51
+ end
52
+
53
+ end
54
+
55
+ spec_files = FileList['spec/*_spec.rb']
56
+
57
+ desc 'Default: run unit tests.'
58
+ task :default => 'spec'
59
+
60
+ task :specs => :spec
61
+ desc "Run all examples"
62
+ Spec::Rake::SpecTask.new('spec') do |t|
63
+ t.spec_opts = ['--color']
64
+ t.spec_files = spec_files
65
+ end
66
+
67
+ namespace :spec do
68
+ desc "Run all examples with RCov"
69
+ Spec::Rake::SpecTask.new('rcov') do |t|
70
+ t.spec_files = spec_files
71
+ t.rcov = true
72
+ t.rcov_dir = "doc/coverage"
73
+ t.rcov_opts = ['--exclude', 'spec,rspec-*,rcov-*,gems']
74
+ t.spec_opts = ['--color']
75
+ end
76
+
77
+ desc "Generate an html report"
78
+ Spec::Rake::SpecTask.new('report') do |t|
79
+ t.spec_files = spec_files
80
+ t.rcov = true
81
+ t.rcov_dir = "doc/coverage"
82
+ t.rcov_opts = ['--exclude', 'spec']
83
+ t.spec_opts = ['--color', "--format", "html:doc/reports/specs.html"]
84
+ t.fail_on_error = false
85
+ end
86
+
87
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'lib', 'even_better_nested_set')
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require File.dirname(__FILE__) + '/nested_set_behavior'
3
+
4
+ class Directory < ActiveRecord::Base
5
+ acts_as_nested_set :left => :lft, :right => :rgt
6
+
7
+ validates_presence_of :name
8
+ end
9
+
10
+ describe Directory do
11
+
12
+ def invalid_attributes(options = {})
13
+ return { }.merge(options)
14
+ end
15
+
16
+ def valid_attributes(options = {})
17
+ $directory_no = $directory_no ? $directory_no + 1 : 0
18
+ return { :name => "directory#{$directory_no}" }.merge(options)
19
+ end
20
+
21
+ before do
22
+ @model = Directory
23
+ end
24
+
25
+ it_should_behave_like "all nested set models"
26
+
27
+ end
@@ -0,0 +1,74 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require File.dirname(__FILE__) + '/nested_set_behavior'
3
+
4
+ class Employee < ActiveRecord::Base
5
+ acts_as_nested_set :scope => :company
6
+
7
+ validates_presence_of :name
8
+ end
9
+
10
+ describe Employee, "with nested sets for two different companies" do
11
+ before do
12
+ # Company 1...
13
+ Employee.with_options :company_id => 1 do |c1|
14
+ @c1_1 = c1.create!(:name => "Company 1 - 1")
15
+ @c1_2 = c1.create!(:name => "Company 1 - 2")
16
+
17
+ @c1_11 = c1.create!(:name => "Company 1 - 11", :parent => @c1_1)
18
+ @c1_12 = c1.create!(:name => "Company 1 - 12", :parent => @c1_1)
19
+
20
+ @c1_111 = c1.create!(:name => "Company 1 - 111", :parent => @c1_11)
21
+ end
22
+
23
+ # Company 2...
24
+ Employee.with_options :company_id => 2 do |c2|
25
+ @c2_1 = c2.create!(:name => "Company 2 - 1")
26
+ @c2_11 = c2.create!(:name => "Company 2 - 11", :parent => @c2_1)
27
+ end
28
+ end
29
+
30
+ after do
31
+ Employee.delete_all
32
+ end
33
+
34
+ it "should not allow a new employee in one company to be a child of an employee in the other company, when parent is assigned to" do
35
+ @employee = Employee.create(:company_id => 1, :parent => @c2_11)
36
+ @employee.errors[:parent_id].should_not be_nil
37
+ end
38
+
39
+ it "should not allow a new employee in one company to be a child of an employee in the other company, when parent_id is assigned to" do
40
+ @employee = Employee.create(:company_id => 1, :parent_id => @c2_11.id)
41
+ @employee.errors[:parent_id].should_not be_nil
42
+ end
43
+
44
+ it "should not allow an existing employee in one company to become a child of an employee in the other company, when parent is assigned to" do
45
+ @c1_11.parent = @c2_11
46
+ @c1_11.save
47
+ @c1_11.errors[:parent_id].should_not be_nil
48
+ end
49
+
50
+ it "should not allow an existing employee in one company to become a child of an employee in the other company, when parent_id is assigned to" do
51
+ @c1_11.parent_id = @c2_11.id
52
+ @c1_11.save
53
+ @c1_11.errors[:parent_id].should_not be_nil
54
+ end
55
+
56
+ it "should keep the tree for company 1 and for company 2 entirely disjoint" do
57
+ c1_tree = (@c1_1.family + @c1_2.family).flatten
58
+ c2_tree = @c2_1.family
59
+
60
+ (c1_tree & c2_tree).should be_empty
61
+ end
62
+
63
+ it "should return the correct descendants when retrieving via a database query" do
64
+ @c1_1.descendants.should == [@c1_11, @c1_111, @c1_12]
65
+ @c1_2.descendants.should == []
66
+ @c2_1.descendants.should == [@c2_11]
67
+ end
68
+
69
+ it "should return the correct levels when retrieving via a database query" do
70
+ @c1_1.family.map { |d| d.level }.should == [0, 1, 2, 1]
71
+ @c1_2.family.map { |d| d.level }.should == [0]
72
+ @c2_1.family.map { |d| d.level }.should == [0, 1]
73
+ end
74
+ end
@@ -0,0 +1,571 @@
1
+ describe "all nested set models", :shared => true do
2
+
3
+ describe @model, 'model with acts_as_nested_set' do
4
+
5
+ before do
6
+ @instance = @model.new(valid_attributes)
7
+ end
8
+
9
+ it "should throw an error when attempting to assign left directly" do
10
+ lambda {
11
+ @instance.left = 42
12
+ }.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
13
+ @instance.left.should_not == 42
14
+ end
15
+
16
+ it "should throw an error when attempting to assign right directly" do
17
+ lambda {
18
+ @instance.right = 42
19
+ }.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
20
+ @instance.right.should_not == 42
21
+ end
22
+
23
+ it "should throw an error when mass assigning to left" do
24
+ lambda {
25
+ @model.new(valid_attributes(:left => 1))
26
+ }.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
27
+ end
28
+
29
+ it "should throw an error when mass assigning to right" do
30
+ lambda {
31
+ @model.new(valid_attributes(:right => 1))
32
+ }.should raise_error(EvenBetterNestedSet::IllegalAssignmentError)
33
+ end
34
+
35
+ it "should change the parent_id in the database when a parent is assigned" do
36
+ without_changing_the_database do
37
+ @parent = @model.create!(valid_attributes)
38
+
39
+ @instance.parent = @parent
40
+ @instance.save!
41
+ @instance = @model.find(@instance.id)
42
+
43
+ @instance.parent_id.should == @parent.id
44
+ end
45
+ end
46
+
47
+ it "should change the parent_id in the database when a parent_id is assigned" do
48
+ without_changing_the_database do
49
+ @parent = @model.create!(valid_attributes)
50
+
51
+ @instance.parent_id = @parent.id
52
+ @instance.save!
53
+ @instance = @model.find(@instance.id)
54
+
55
+ @instance.parent_id.should == @parent.id
56
+ end
57
+ end
58
+
59
+ describe '#bounds' do
60
+
61
+ it "should return a range, from left to right" do
62
+ without_changing_the_database do
63
+ @instance.save!
64
+ @instance.left.should == 1
65
+ @instance.right.should == 2
66
+ @instance.bounds.should == (1..2)
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+
74
+ describe @model, "with many descendants" do
75
+ before do
76
+ @r1 = @model.create!(valid_attributes)
77
+ @r2 = @model.create!(valid_attributes)
78
+ @r3 = @model.create!(valid_attributes)
79
+
80
+ @r1c1 = @model.create!(valid_attributes(:parent => @r1))
81
+ @r1c2 = @model.create!(valid_attributes(:parent => @r1))
82
+ @r1c3 = @model.create!(valid_attributes(:parent => @r1))
83
+ @r2c1 = @model.create!(valid_attributes(:parent => @r2))
84
+
85
+ @r1c1s1 = @model.create!(valid_attributes(:parent => @r1c1))
86
+ @r1c2s1 = @model.create!(valid_attributes(:parent => @r1c2))
87
+ @r1c2s2 = @model.create!(valid_attributes(:parent => @r1c2))
88
+ @r1c2s3 = @model.create!(valid_attributes(:parent => @r1c2))
89
+
90
+ @r1c2s2m1 = @model.create!(valid_attributes(:parent => @r1c2s2))
91
+ end
92
+
93
+ after do
94
+ @model.delete_all
95
+ end
96
+
97
+ it "should find all root nodes" do
98
+ @model.roots.all.should == [@r1, @r2, @r3]
99
+ end
100
+
101
+ it "should find a root nodes" do
102
+ @model.roots.first.should == @r1
103
+ end
104
+
105
+ it "should maintain the integrity of the tree if a node is deleted" do
106
+ @r1c2.destroy
107
+
108
+ @r1.reload
109
+ @r1c3.reload
110
+
111
+ @r1.bounds.should == (1..8)
112
+ @r1c3.bounds.should == (6..7)
113
+ end
114
+
115
+ it "should maintain the integrity of the tree if a node is moved" do
116
+ @r1c2.parent_id = @r2.id
117
+ @r1c2.save!
118
+
119
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
120
+
121
+ @r1.bounds.should == (1..8)
122
+ @r1c3.bounds.should == (6..7)
123
+ @r2.bounds.should == (9..22)
124
+ @r1c2.bounds.should == (12..21)
125
+ @r1c2s1.bounds.should == (13..14)
126
+ end
127
+
128
+ it "should change the parent, left and right in the database when a node is moved" do
129
+
130
+ @r1c2.parent_id = @r2.id
131
+
132
+ @r1c2.save!
133
+ @r1c2 = @model.find(@r1c2.id)
134
+
135
+ @r1c2.bounds.should == (12..21)
136
+ @r1c2.parent_id.should == @r2.id
137
+ end
138
+
139
+ it "should maintain the integrity of the tree if a node is moved to a root position" do
140
+ @r1c2.parent_id = nil
141
+ @r1c2.save!
142
+
143
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
144
+
145
+ @r1.bounds.should == (1..8)
146
+ @r1c3.bounds.should == (6..7)
147
+ @r1c2.bounds.should == (15..24)
148
+ @r1c2s1.bounds.should == (16..17)
149
+ end
150
+
151
+ it "should maintain the integrity of the tree if a node is moved to a root position by assigning a blank string (mass assignment)" do
152
+ @r1c2.parent_id = ""
153
+ @r1c2.save!
154
+
155
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
156
+
157
+ @r1.bounds.should == (1..8)
158
+ @r1c3.bounds.should == (6..7)
159
+ @r1c2.bounds.should == (15..24)
160
+ @r1c2s1.bounds.should == (16..17)
161
+ end
162
+
163
+ it "should maintain the integrity of the tree if a root is to a non-root position" do
164
+ @r1c2.reload
165
+ @r2.parent_id = @r1c2.id
166
+ @r2.save!
167
+
168
+ reload_models(@r1, @r2, @r2c1, @r1c3, @r3, @r1c2)
169
+
170
+ @r1.bounds.should == (1..22)
171
+ @r1c2.bounds.should == (6..19)
172
+ @r1c3.bounds.should == (20..21)
173
+ @r3.bounds.should == (23..24)
174
+ @r2.bounds.should == (15..18)
175
+ @r2c1.bounds.should == (16..17)
176
+ end
177
+
178
+ it "should maintain the integrity of the tree if a node is moved through the parent association" do
179
+ @r1c2.parent = @r2
180
+ @r1c2.save!
181
+
182
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
183
+
184
+ @r1.bounds.should == (1..8)
185
+ @r1c3.bounds.should == (6..7)
186
+ @r2.bounds.should == (9..22)
187
+ @r1c2.bounds.should == (12..21)
188
+ @r1c2s1.bounds.should == (13..14)
189
+ end
190
+
191
+ it "should maintain the integrity of the tree if a node is moved to a root position through the parent association" do
192
+ @r1c2.parent = nil
193
+ @r1c2.save!
194
+
195
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
196
+
197
+ @r1.bounds.should == (1..8)
198
+ @r1c3.bounds.should == (6..7)
199
+ @r1c2.bounds.should == (15..24)
200
+ @r1c2s1.bounds.should == (16..17)
201
+ end
202
+
203
+ it "should maintain the integrity of the tree if a root is to a non-root position through the parent association" do
204
+ @r1c2.reload
205
+ @r2.parent = @r1c2
206
+ @r2.save!
207
+
208
+ reload_models(@r1, @r2, @r2c1, @r1c3, @r3, @r1c2)
209
+
210
+ @r1.bounds.should == (1..22)
211
+ @r1c2.bounds.should == (6..19)
212
+ @r1c3.bounds.should == (20..21)
213
+ @r3.bounds.should == (23..24)
214
+ @r2.bounds.should == (15..18)
215
+ @r2c1.bounds.should == (16..17)
216
+ end
217
+
218
+ it "should be invalid if parent is a descendant" do
219
+ @r2.parent = @r2c1
220
+ @r2.should_not be_valid
221
+ end
222
+
223
+ it "should be invalid if parent is self" do
224
+ @r2.parent = @r2
225
+ @r2.should_not be_valid
226
+ end
227
+
228
+ describe ".nested_set" do
229
+ it "should find all nodes as a nested set and cache that data" do
230
+ roots = @model.nested_set
231
+
232
+ @model.delete_all
233
+
234
+ roots[0].should == @r1
235
+ roots[0].children[0].should == @r1c1
236
+ roots[0].children[0].children[0].should == @r1c1s1
237
+ roots[0].children[1].should == @r1c2
238
+ roots[0].children[1].children[0].should == @r1c2s1
239
+ roots[0].children[1].children[1].should == @r1c2s2
240
+ roots[0].children[1].children[1].children[0].should == @r1c2s2m1
241
+ roots[0].children[1].children[2].should == @r1c2s3
242
+ roots[0].children[2].should == @r1c3
243
+ roots[1].should == @r2
244
+ roots[1].children[0].should == @r2c1
245
+ roots[2].should == @r3
246
+
247
+ roots[1].children[0].parent.should == @r2
248
+ end
249
+ end
250
+
251
+ describe ".sort_nodes_to_nested_set" do
252
+ it "should accept a list of nodes and sort them to a nested set" do
253
+ roots = @model.sort_nodes_to_nested_set(@model.find(:all))
254
+ roots[0].should == @r1
255
+ roots[0].children[0].should == @r1c1
256
+ roots[0].children[0].children[0].should == @r1c1s1
257
+ roots[0].children[1].should == @r1c2
258
+ roots[0].children[1].children[0].should == @r1c2s1
259
+ roots[0].children[1].children[1].should == @r1c2s2
260
+ roots[0].children[1].children[1].children[0].should == @r1c2s2m1
261
+ roots[0].children[1].children[2].should == @r1c2s3
262
+ roots[0].children[2].should == @r1c3
263
+ roots[1].should == @r2
264
+ roots[1].children[0].should == @r2c1
265
+ roots[2].should == @r3
266
+ end
267
+ end
268
+
269
+ describe ".recalculate_nested_set" do
270
+ def values
271
+ @model.find(:all, :order => :id).map { |node| [node.left, node.right] }
272
+ end
273
+
274
+ before do
275
+ @model.find(:all, :order => :id).each do |i|
276
+ i.send(:set_boundaries, rand(1000), rand(1000))
277
+ i.save_without_validation!
278
+ end
279
+ end
280
+
281
+ it "should correctly restore the left and right values for a messed up nested set" do
282
+ @model.recalculate_nested_set
283
+ [@r1, @r2, @r3].each(&:reload)
284
+
285
+ expected = [
286
+ [@r1c1, @r1c2, @r1c3, @r1c1s1, @r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1],
287
+ [@r2c1],
288
+ [],
289
+ [@r1c1s1],
290
+ [@r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1],
291
+ [],
292
+ [],
293
+ [],
294
+ [],
295
+ [@r1c2s2m1],
296
+ [],
297
+ []
298
+ ]
299
+
300
+ [@r1, @r2, @r3, @r1c1, @r1c2, @r1c3, @r2c1, @r1c1s1, @r1c2s1, @r1c2s2, @r1c2s3, @r1c2s2m1].each_with_index do |node, i|
301
+ node.descendants.find(:all, :order => :id).should == expected[i]
302
+ end
303
+ end
304
+
305
+ it "should leave all records valid after running" do
306
+ @model.recalculate_nested_set
307
+ @model.find(:all).each do |node|
308
+ node.should be_valid
309
+ end
310
+ end
311
+ end
312
+
313
+ describe "#cache_nested_set" do
314
+ it "should cache all descendant nodes so that calls to #children or #parent don't hit the database" do
315
+ @r1c2.cache_nested_set
316
+
317
+ @model.delete_all
318
+
319
+ @r1c2.children[0].should == @r1c2s1
320
+ @r1c2.children[1].should == @r1c2s2
321
+ @r1c2.children[1].children[0].should == @r1c2s2m1
322
+ @r1c2.children[2].should == @r1c2s3
323
+
324
+ @r1c2.children[1].children[0].parent.should == @r1c2s2
325
+ end
326
+ end
327
+
328
+ describe "#parent" do
329
+ it "should find the parent node" do
330
+ @r1c1.parent.should == @r1
331
+ @r1c2s2.parent.should == @r1c2
332
+ @r1c2s2m1.parent.should == @r1c2s2
333
+ end
334
+ end
335
+
336
+ describe "#children" do
337
+ it "should find all nodes that are direct descendants of this one" do
338
+ @r1.children.should == [@r1c1, @r1c2, @r1c3]
339
+ @r1c2s2.children.should == [@r1c2s2m1]
340
+ end
341
+
342
+ it "should allow creation of children" do
343
+ child = @r1c2.children.create!(valid_attributes)
344
+
345
+ child.parent_id.should == @r1c2.id
346
+ child.bounds.should == (15..16)
347
+ end
348
+
349
+ it "should allow addition of children" do
350
+ @r2.children << @r1c2
351
+
352
+ reload_models(@r1, @r1c3, @r2, @r1c2, @r1c2s1)
353
+
354
+ @r1.bounds.should == (1..8)
355
+ @r1c3.bounds.should == (6..7)
356
+ @r2.bounds.should == (9..22)
357
+ @r1c2.bounds.should == (12..21)
358
+ @r1c2s1.bounds.should == (13..14)
359
+ end
360
+ end
361
+
362
+ describe "#patriarch" do
363
+ it "should find the root node that this node descended from" do
364
+ @r1c1.patriarch.should == @r1
365
+ @r1c2s2.patriarch.should == @r1
366
+ @r1c2s2m1.patriarch.should == @r1
367
+ @r2c1.patriarch.should == @r2
368
+ @r1.patriarch.should == @r1
369
+ end
370
+ end
371
+
372
+ describe "#root" do
373
+ it "should find the root node that this node descended from" do
374
+ @r1c1.root.should == @r1
375
+ @r1c2s2.root.should == @r1
376
+ @r1c2s2m1.root.should == @r1
377
+ @r2c1.root.should == @r2
378
+ @r1.root.should == @r1
379
+ end
380
+ end
381
+
382
+ describe "#root?" do
383
+ it "should be true if node doesn't have a parent" do
384
+ @r1.should be_root
385
+ @model.roots.should include(@r1)
386
+ @r1.parent.should be_nil
387
+ end
388
+ end
389
+
390
+ describe "#descendant_of(other_node)" do
391
+ it "should be true if other_node is an ancestor of node" do
392
+ reload_models @r1, @r1c2s2
393
+
394
+ @r1c2s2.should be_descendant_of(@r1)
395
+ @r1c2s2.ancestors.should include(@r1)
396
+ @r1.descendants.should include(@r1c2s2)
397
+ end
398
+ end
399
+
400
+ describe "#generation" do
401
+ it "should find all nodes in the same generation as this one for a root node" do
402
+ @r1.generation.should == [@r1, @r2, @r3]
403
+ end
404
+
405
+ it "should find all nodes in the same generation as this one" do
406
+ @r1c1.generation.should == [@r1c1, @r1c2, @r1c3]
407
+ end
408
+ end
409
+
410
+ describe "#siblings" do
411
+ it "should find all sibling nodes for a root node" do
412
+ @r1.siblings.should == [@r2, @r3]
413
+ end
414
+
415
+ it "should find all sibling nodes for a child node" do
416
+ @r1c1.siblings.should == [@r1c2, @r1c3]
417
+ end
418
+ end
419
+
420
+ describe "#descendants" do
421
+ it "should find all descendants of this node" do
422
+ @r1.descendants.should == [@r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
423
+ end
424
+ end
425
+
426
+ describe "#family" do
427
+ it "should combine self and descendants" do
428
+ @r1.family.should == [@r1, @r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
429
+ end
430
+ end
431
+
432
+ describe "#family_ids" do
433
+ it "should find all ids of the node's nested set" do
434
+ @r1c1.family_ids.should == [@r1c1.id, @r1c1s1.id]
435
+ @r1c2.family_ids.should == [@r1c2.id, @r1c2s1.id, @r1c2s2.id, @r1c2s2m1.id, @r1c2s3.id]
436
+ end
437
+ end
438
+
439
+ describe "#ancestors" do
440
+ it "should return a node's parent and its parent's parents" do
441
+ @r1c2s2m1.ancestors.should == [@r1c2s2, @r1c2, @r1]
442
+ end
443
+ end
444
+
445
+ describe "#lineage" do
446
+ it "should return a node, it's parent and its parent's parents" do
447
+ @r1c2s2m1.lineage.should == [@r1c2s2m1, @r1c2s2, @r1c2, @r1]
448
+ end
449
+ end
450
+
451
+ describe "#level" do
452
+ it "should give the depth from the node to its root" do
453
+ @r1.level.should == 0
454
+ @r1c2.level.should == 1
455
+ @r1c2s2.level.should == 2
456
+ @r1c2s2m1.level.should == 3
457
+ end
458
+ end
459
+
460
+ describe "#kin" do
461
+ it "should find the patriarch and all its descendants" do
462
+ @r1c2s2.kin.should == [@r1, @r1c1, @r1c1s1, @r1c2, @r1c2s1, @r1c2s2, @r1c2s2m1, @r1c2s3, @r1c3]
463
+ end
464
+ end
465
+
466
+ end
467
+
468
+ describe @model, "with acts_as_nested_set" do
469
+
470
+ it "should add a new root node if the parent is not set" do
471
+ without_changing_the_database do
472
+ @instance = @model.create!(valid_attributes)
473
+ @instance.parent_id.should be_nil
474
+
475
+ @instance.bounds.should == (1..2)
476
+ end
477
+ end
478
+
479
+ it "should add a new root node if the parent is not set and there already are some root nodes" do
480
+ without_changing_the_database do
481
+ @model.create!(valid_attributes)
482
+ @model.create!(valid_attributes)
483
+ @instance = @model.create!(valid_attributes)
484
+ @instance.reload
485
+
486
+ @instance.parent_id.should be_nil
487
+ @instance.bounds.should == (5..6)
488
+ end
489
+ end
490
+
491
+ it "should append a child node to a parent" do
492
+ without_changing_the_database do
493
+ @parent = @model.create!(valid_attributes)
494
+ @parent.bounds.should == (1..2)
495
+
496
+ @instance = @model.create!(valid_attributes(:parent => @parent))
497
+
498
+ @parent.reload
499
+
500
+ @instance.parent.should == @parent
501
+
502
+ @instance.bounds.should == (2..3)
503
+ @parent.bounds.should == (1..4)
504
+ end
505
+ end
506
+
507
+ it "should rollback changes if the save is not successfull for some reason" do
508
+ without_changing_the_database do
509
+ @parent = @model.create!(valid_attributes)
510
+ @parent.bounds.should == (1..2)
511
+
512
+ @instance = @model.create(invalid_attributes(:parent => @parent))
513
+ @instance.should be_a_new_record
514
+
515
+ @parent.reload
516
+
517
+ @parent.bounds.should == (1..2)
518
+ end
519
+ end
520
+
521
+ it "should append a child node to a parent and shift other nodes out of the way" do
522
+ without_changing_the_database do
523
+ @root1 = @model.create!(valid_attributes)
524
+ @root2 = @model.create!(valid_attributes)
525
+
526
+ @root1.bounds.should == (1..2)
527
+ @root2.bounds.should == (3..4)
528
+
529
+ @child1 = @model.create!(valid_attributes(:parent => @root1))
530
+ reload_models(@root1, @root2)
531
+
532
+ @root1.bounds.should == (1..4)
533
+ @root2.bounds.should == (5..6)
534
+ @child1.bounds.should == (2..3)
535
+
536
+ @child2 = @model.create!(valid_attributes(:parent => @root1))
537
+ reload_models(@root1, @root2, @child1)
538
+
539
+ @root1.bounds.should == (1..6)
540
+ @root2.bounds.should == (7..8)
541
+ @child1.bounds.should == (2..3)
542
+ @child2.bounds.should == (4..5)
543
+
544
+ @subchild1 = @model.create!(valid_attributes(:parent => @child2))
545
+ reload_models(@root1, @root2, @child1, @child2)
546
+
547
+ @root1.bounds.should == (1..8)
548
+ @root2.bounds.should == (9..10)
549
+ @child1.bounds.should == (2..3)
550
+ @child2.bounds.should == (4..7)
551
+ @subchild1.bounds.should == (5..6)
552
+
553
+ @subchild2 = @model.create!(valid_attributes(:parent => @child1))
554
+ reload_models(@root1, @root2, @child1, @child2, @subchild1)
555
+
556
+ @root1.bounds.should == (1..10)
557
+ @root2.bounds.should == (11..12)
558
+ @child1.bounds.should == (2..5)
559
+ @child2.bounds.should == (6..9)
560
+ @subchild1.bounds.should == (7..8)
561
+ @subchild2.bounds.should == (3..4)
562
+ end
563
+ end
564
+
565
+ end
566
+
567
+ def reload_models(*attrs)
568
+ attrs.each {|m| m.reload }
569
+ end
570
+
571
+ end
@@ -0,0 +1,83 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ # require 'ruby-debug' # Slows down the tests massively
7
+
8
+ require 'eb_nested_set'
9
+
10
+ require 'spec'
11
+
12
+ # change this if sqlite is unavailable
13
+ dbconfig = case ENV["DB"]
14
+ when "postgresql"
15
+ {
16
+ :adapter => 'postgresql',
17
+ :database => 'even_better_nested_set_test',
18
+ :host => '127.0.0.1'
19
+ }
20
+ when "mysql"
21
+ {
22
+ :adapter => 'mysql',
23
+ :database => 'even_better_nested_set_test',
24
+ :host => '127.0.0.1'
25
+ }
26
+ else
27
+ {
28
+ :adapter => 'sqlite3',
29
+ :database => File.join(File.dirname(__FILE__), 'db', 'test.sqlite3')
30
+ }
31
+ end
32
+
33
+ ActiveRecord::Base.establish_connection(dbconfig)
34
+ ActiveRecord::Migration.verbose = false
35
+
36
+ def show_model_variables_for(context, model)
37
+ context.instance_variables.sort.each do |i|
38
+ m = eval(i)
39
+ if m.is_a?(model)
40
+ m.reload
41
+ puts "#{i.ljust(8)}\t#{m.left}\t#{m.right}\t#{m.name}"
42
+ end
43
+ end
44
+ end
45
+
46
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
47
+
48
+
49
+ class TestMigration < ActiveRecord::Migration
50
+ def self.up
51
+ create_table :directories, :force => true do |t|
52
+ t.column :lft, :integer
53
+ t.column :rgt, :integer
54
+ t.column :parent_id, :integer
55
+ t.column :name, :string
56
+ end
57
+
58
+ create_table :employees, :force => true do |t|
59
+ t.column :left, :integer
60
+ t.column :right, :integer
61
+ t.column :parent_id, :integer
62
+ t.column :name, :string
63
+ t.column :company_id, :integer
64
+ end
65
+ end
66
+
67
+ def self.down
68
+ drop_table :directories
69
+ drop_table :employees
70
+ rescue
71
+ nil
72
+ end
73
+ end
74
+
75
+ def without_changing_the_database
76
+ ActiveRecord::Base.transaction do
77
+ yield
78
+ raise ActiveRecord::Rollback
79
+ end
80
+ end
81
+
82
+ TestMigration.down
83
+ TestMigration.up
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jnicklas-even_better_nested_set
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.2
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Nicklas
8
+ autorequire: even_better_nested_set
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-12 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A cool acts_as_nested_set alternative
17
+ email: jonas.nicklas@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ - LICENSE
25
+ files:
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - init.rb
30
+ - lib/even_better_nested_set.rb
31
+ - spec/directory_spec.rb
32
+ - spec/employee_spec.rb
33
+ - spec/nested_set_behavior.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/jnicklas/even_better_nested_set/tree/master
37
+ post_install_message:
38
+ rdoc_options: []
39
+
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.2.0
58
+ signing_key:
59
+ specification_version: 2
60
+ summary: A cool acts_as_nested_set alternative
61
+ test_files: []
62
+