nbrew-better_nested_set 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ module SymetrieCom
2
+ module Acts #:nodoc:
3
+
4
+ # This module provides some helpers for the model classes using acts_as_nested_set.
5
+ # It is included by default in all views. If you need to remove it, edit the last line
6
+ # of init.rb.
7
+ #
8
+ module BetterNestedSetHelper
9
+
10
+ # Prints a line of ancestors with links, on the form
11
+ # root > parent > item
12
+ #
13
+ # == Usage
14
+ # Default is to use links to {your_cotroller}/show with the first string column of your model.
15
+ # You can tweak this by passing your parameters, or better, pass a block that will receive
16
+ # an item from your nested set tree and a boolean flag (true for current item) and that
17
+ # should return the line with the link.
18
+ #
19
+ # == Examples
20
+ #
21
+ # nested_set_full_outline(category)
22
+ #
23
+ # # non standard actions and separators
24
+ # nested_set_full_outline(category, :action => :search, :separator => ' | ')
25
+ #
26
+ # # with a block that will return the link to the item
27
+ # # note that the current item will lead to another action
28
+ # nested_set_full_outline(category) { |item, current?|
29
+ # if current?
30
+ # link_to "#{item.name} (#{item.})", product_url(:action => :show_category, :category => item.whole_url)
31
+ # else
32
+ # link_to "#{item.name} (#{item.})", category_url(:action => :browse, :criteria => item.whole_url)
33
+ # end
34
+ # }
35
+ #
36
+ # == Params are:
37
+ # +item+ - the object to display
38
+ # +hash+ - containing :
39
+ # * +text_column+ - the title column, defaults to the first string column of the model
40
+ # * +:action+ - the action to be called (defaults to :show)
41
+ # * +:controller+ - the controller name (defaults to the model name)
42
+ # * +:separator+ - the separator (defaults to >)
43
+ # * +&block+ - a block { |item, current?| ... item.name }
44
+ #
45
+ def nested_set_full_outline(item, options={})
46
+ return if item.nil?
47
+ raise 'Not a nested set model !' unless item.respond_to?(:acts_as_nested_set_options)
48
+
49
+ options = {
50
+ :text_column => options[:text_column] || item.acts_as_nested_set_options[:text_column],
51
+ :action => options[:action] || :show,
52
+ :controller => options[:controller] || item.class.to_s.underscore,
53
+ :separator => options[:separator] || ' > ' }
54
+
55
+ s = ''
56
+ for it in item.ancestors
57
+ if block_given?
58
+ s += yield(it) + options[:separator]
59
+ else
60
+ s += link_to( it[options[:text_column]], { :controller => options[:controller], :action => options[:action], :id => it }) + options[:separator]
61
+ end
62
+ end
63
+ if block_given?
64
+ s + yield(item)
65
+ else
66
+ s + h(item[options[:text_column]])
67
+ end
68
+ end
69
+
70
+ # Returns options for select.
71
+ # You can exclude some items from the tree.
72
+ # You can pass a block receiving an item and returning the string displayed in the select.
73
+ #
74
+ # == Usage
75
+ # Default is to use the whole tree and to print the first string column of your model.
76
+ # You can tweak this by passing your parameters, or better, pass a block that will receive
77
+ # an item from your nested set tree and that should return the line with the link.
78
+ #
79
+ # == Examples
80
+ #
81
+ # nested_set_options_for_select(Category)
82
+ #
83
+ # # show only a part of the tree, and exclude a category and its subtree
84
+ # nested_set_options_for_select(selected_category, :exclude => category)
85
+ #
86
+ # # add a custom string
87
+ # nested_set_options_for_select(Category, :exclude => category) { |item| "#{' ' * item.level}#{item.name} (#{item.url})" }
88
+ #
89
+ # == Params
90
+ # * +class_or_item+ - Class name or item or array of items to start the display with
91
+ # * +text_column+ - the title column, defaults to the first string column of the model
92
+ # * +&block+ - a block { |item| ... item.name }
93
+ # If no block passed, uses {|item| "#{'··' * item.level}#{item[text_column]}"}
94
+ def nested_set_options_for_select(class_or_item, options=nil)
95
+ # find class
96
+ if class_or_item.is_a? Class
97
+ first_item = class_or_item.roots
98
+ else
99
+ first_item = class_or_item
100
+ end
101
+ return [] if first_item.nil?
102
+ raise 'Not a nested set model !' unless class_or_item.respond_to?(:acts_as_nested_set_options)
103
+
104
+ # exclude some items and all their children
105
+ if options.is_a? Hash
106
+ text_column = options[:text_column]
107
+ options.delete_if { |key, value| key != :exclude }
108
+ else
109
+ options = nil
110
+ end
111
+ text_column ||= class_or_item.acts_as_nested_set_options[:text_column]
112
+
113
+ if first_item.is_a?(Array)
114
+ tree = first_item.collect{|i| i.full_set(options)}.flatten
115
+ else
116
+ tree = first_item.full_set(options)
117
+ end
118
+ if block_given?
119
+ tree.map{|item| [yield(item), item.id] }
120
+ else
121
+ tree.map{|item| [ "#{'··' * item.level}#{item[text_column]}", item.id]}
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
@@ -0,0 +1,14 @@
1
+ # better_nested_set
2
+ # (c) 2005 Jean-Christophe Michel
3
+ # MIT licence
4
+ #
5
+ require 'better_nested_set'
6
+ require 'better_nested_set_helper'
7
+
8
+ ActiveRecord::Base.class_eval do
9
+ include SymetrieCom::Acts::NestedSet
10
+ end
11
+
12
+ if Object.const_defined?('ActionView')
13
+ ActionView::Base.send :include, SymetrieCom::Acts::BetterNestedSetHelper
14
+ end
@@ -0,0 +1 @@
1
+ See instructions in ../README
@@ -0,0 +1,25 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
3
+ require 'test_help'
4
+
5
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
6
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
7
+ cs = ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'mysql'])
8
+ puts "Using #{cs.config[:adapter]} adapter"
9
+ load(File.dirname(__FILE__) + '/schema.rb')
10
+
11
+ Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + '/fixtures/'
12
+ $LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
13
+
14
+ class Test::Unit::TestCase #:nodoc:
15
+ def create_fixtures(*table_names)
16
+ if block_given?
17
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
18
+ else
19
+ Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
20
+ end
21
+ end
22
+
23
+ self.use_transactional_fixtures = true
24
+ self.use_instantiated_fixtures = false
25
+ end
@@ -0,0 +1,1368 @@
1
+ require File.dirname(__FILE__) + '/abstract_unit'
2
+ require File.dirname(__FILE__) + '/fixtures/mixin'
3
+ require 'pp'
4
+
5
+ class MixinNestedSetTest < Test::Unit::TestCase
6
+ fixtures :mixins
7
+
8
+ def setup
9
+ # force so other tests besides test_destroy_dependent aren't affected
10
+ NestedSetWithStringScope.acts_as_nested_set_options[:dependent] = :delete_all
11
+ end
12
+
13
+ ##########################################
14
+ # HIGH LEVEL TESTS
15
+ ##########################################
16
+ def test_mixing_in_methods
17
+ ns = NestedSet.new
18
+ assert(ns.respond_to?(:all_children)) # test a random method
19
+
20
+ check_method_mixins(ns)
21
+ check_deprecated_method_mixins(ns)
22
+ check_class_method_mixins(NestedSet)
23
+ end
24
+
25
+ def check_method_mixins(obj)
26
+ [:<=>, :all_children, :all_children_count, :ancestors, :before_create, :before_destroy, :check_full_tree,
27
+ :check_subtree, :children, :children_count, :full_set, :leaves, :leaves_count, :left_col_name, :level, :move_to_child_of,
28
+ :move_to_left_of, :move_to_right_of, :parent, :parent_col_name, :renumber_full_tree, :right_col_name,
29
+ :root, :roots, :self_and_ancestors, :self_and_siblings, :siblings].each { |symbol| assert(obj.respond_to?(symbol)) }
30
+ end
31
+
32
+ def check_deprecated_method_mixins(obj)
33
+ [:add_child, :direct_children, :parent_column, :root?, :child?, :unknown?].each { |symbol| assert(obj.respond_to?(symbol)) }
34
+ end
35
+
36
+ def check_class_method_mixins(klass)
37
+ [:root, :roots, :check_all, :renumber_all].each { |symbol| assert(klass.respond_to?(symbol)) }
38
+ end
39
+
40
+ def test_string_scope
41
+ ns = NestedSet.new
42
+ assert_equal("mixins.root_id IS NULL", ns.scope_condition)
43
+
44
+ ns = NestedSetWithStringScope.new
45
+ ns.root_id = 1
46
+ assert_equal("mixins.root_id = 1", ns.scope_condition)
47
+ ns.root_id = 42
48
+ assert_equal("mixins.root_id = 42", ns.scope_condition)
49
+ check_method_mixins ns
50
+ end
51
+
52
+ def test_without_scope_condition
53
+ ns = NestedSet.new
54
+ assert_equal("mixins.root_id IS NULL", ns.scope_condition)
55
+ NestedSet.without_scope_condition do
56
+ assert_equal("(1 = 1)", ns.scope_condition)
57
+ end
58
+ assert_equal("mixins.root_id IS NULL", ns.scope_condition)
59
+ end
60
+
61
+ def test_symbol_scope
62
+ ns = NestedSetWithSymbolScope.new
63
+ ns.root_id = 1
64
+ assert_equal("mixins.root_id = 1", ns.scope_condition)
65
+ ns.root_id = 42
66
+ assert_equal("mixins.root_id = 42", ns.scope_condition)
67
+ check_method_mixins ns
68
+ end
69
+
70
+ def test_protected_attributes
71
+ ns = NestedSet.new(:parent_id => 2, :lft => 3, :rgt => 2)
72
+ [:parent_id, :lft, :rgt].each {|symbol| assert_equal(nil, ns.send(symbol))}
73
+ end
74
+
75
+ def test_really_protected_attributes
76
+ ns = NestedSet.new
77
+ assert_raise(ActiveRecord::ActiveRecordError) {ns.parent_id = 1}
78
+ assert_raise(ActiveRecord::ActiveRecordError) {ns.lft = 1}
79
+ assert_raise(ActiveRecord::ActiveRecordError) {ns.rgt = 1}
80
+ end
81
+
82
+ ##########################################
83
+ # CLASS METHOD TESTS
84
+ ##########################################
85
+ def test_class_root
86
+ NestedSetWithStringScope.roots.each {|r| r.destroy unless r.id == 4001}
87
+ assert_equal([NestedSetWithStringScope.find(4001)], NestedSetWithStringScope.roots)
88
+ NestedSetWithStringScope.find(4001).destroy
89
+ assert_equal(nil, NestedSetWithStringScope.root)
90
+ ns = NestedSetWithStringScope.create(:root_id => 2)
91
+ assert_equal(ns, NestedSetWithStringScope.root)
92
+ end
93
+
94
+ def test_class_root_again
95
+ NestedSetWithStringScope.roots.each {|r| r.destroy unless r.id == 101}
96
+ assert_equal(NestedSetWithStringScope.find(101), NestedSetWithStringScope.root)
97
+ end
98
+
99
+ def test_class_roots
100
+ assert_equal(2, NestedSetWithStringScope.roots.size)
101
+ assert_equal(10, NestedSet.roots.size) # May change if STI behavior changes
102
+ end
103
+
104
+ def test_check_all_1
105
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
106
+ NestedSetWithStringScope.update_all("lft = 3", "id = 103")
107
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
108
+ end
109
+
110
+ def test_check_all_2
111
+ NestedSetWithStringScope.update_all("lft = lft + 1", "lft > 11 AND root_id = 101")
112
+ NestedSetWithStringScope.update_all("rgt = rgt + 1", "lft > 11 AND root_id = 101")
113
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
114
+ end
115
+
116
+ def test_check_all_3
117
+ NestedSetWithStringScope.update_all("lft = lft + 2", "lft > 11 AND root_id = 101")
118
+ NestedSetWithStringScope.update_all("rgt = rgt + 2", "lft > 11 AND root_id = 101")
119
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
120
+ end
121
+
122
+ def test_check_all_4
123
+ ns = NestedSetWithStringScope.create(:root_id => 101) # virtual root
124
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
125
+ NestedSetWithStringScope.update_all("rgt = rgt + 2, lft = lft + 2", "id = #{ns.id}") # create a gap between virtual roots
126
+ assert_nothing_raised {ns.check_subtree}
127
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
128
+ end
129
+
130
+ def test_renumber_all
131
+ NestedSetWithStringScope.update_all("lft = NULL, rgt = NULL")
132
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
133
+ NestedSetWithStringScope.renumber_all
134
+ assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
135
+ NestedSetWithStringScope.update_all("lft = 1, rgt = 2")
136
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
137
+ NestedSetWithStringScope.renumber_all
138
+ assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
139
+ end
140
+
141
+ def test_sql_for
142
+ assert_equal("1 != 1", Category.sql_for([]))
143
+ c = Category.new
144
+ assert_equal("1 != 1", Category.sql_for(c))
145
+ assert_equal("1 != 1", Category.sql_for([c]))
146
+ c.save
147
+ assert_equal("((mixins.lft BETWEEN 1 AND 2))", Category.sql_for(c))
148
+ assert_equal("((mixins.lft BETWEEN 1 AND 2))", Category.sql_for([c]))
149
+ assert_equal("((mixins.lft BETWEEN 1 AND 20))", NestedSetWithStringScope.sql_for(101))
150
+ assert_equal("((mixins.lft BETWEEN 1 AND 20) OR (mixins.lft BETWEEN 4 AND 11))", NestedSetWithStringScope.sql_for([101, set2(3)]))
151
+ assert_equal("((mixins.lft BETWEEN 5 AND 6) OR (mixins.lft BETWEEN 7 AND 8) OR (mixins.lft BETWEEN 9 AND 10))", NestedSetWithStringScope.sql_for(set2(3).children))
152
+ end
153
+
154
+
155
+ ##########################################
156
+ # CALLBACK TESTS
157
+ ##########################################
158
+ # If we change behavior of virtual roots, this test may change
159
+ def test_before_create
160
+ ns = NestedSetWithSymbolScope.create(:root_id => 1234)
161
+ assert_equal(1, ns.lft)
162
+ assert_equal(2, ns.rgt)
163
+ ns = NestedSetWithSymbolScope.create(:root_id => 1234)
164
+ assert_equal(3, ns.lft)
165
+ assert_equal(4, ns.rgt)
166
+ end
167
+
168
+ # test pruning a branch. only works if we allow the deletion of nodes with children
169
+ def test_destroy
170
+ big_tree = NestedSetWithStringScope.find(4001)
171
+
172
+ # Make sure we have the right one
173
+ assert_equal(3, big_tree.direct_children.length)
174
+ assert_equal(10, big_tree.full_set.length)
175
+
176
+ NestedSetWithStringScope.find(4005).destroy
177
+
178
+ big_tree = NestedSetWithStringScope.find(4001)
179
+
180
+ assert_equal(7, big_tree.full_set.length)
181
+ assert_equal(2, big_tree.direct_children.length)
182
+
183
+ assert_equal(1, NestedSetWithStringScope.find(4001).lft)
184
+ assert_equal(2, NestedSetWithStringScope.find(4002).lft)
185
+ assert_equal(3, NestedSetWithStringScope.find(4003).lft)
186
+ assert_equal(4, NestedSetWithStringScope.find(4003).rgt)
187
+ assert_equal(5, NestedSetWithStringScope.find(4004).lft)
188
+ assert_equal(6, NestedSetWithStringScope.find(4004).rgt)
189
+ assert_equal(7, NestedSetWithStringScope.find(4002).rgt)
190
+ assert_equal(8, NestedSetWithStringScope.find(4008).lft)
191
+ assert_equal(9, NestedSetWithStringScope.find(4009).lft)
192
+ assert_equal(10, NestedSetWithStringScope.find(4009).rgt)
193
+ assert_equal(11, NestedSetWithStringScope.find(4010).lft)
194
+ assert_equal(12, NestedSetWithStringScope.find(4010).rgt)
195
+ assert_equal(13, NestedSetWithStringScope.find(4008).rgt)
196
+ assert_equal(14, NestedSetWithStringScope.find(4001).rgt)
197
+ end
198
+
199
+ def test_destroy_2
200
+ assert_nothing_raised {set2(1).check_subtree}
201
+ assert set2(10).destroy
202
+ assert_nothing_raised {set2(1).reload.check_subtree}
203
+ assert set2(9).children.empty?
204
+ assert set2(9).destroy
205
+ assert_equal 15, set2(4).rgt
206
+ assert_nothing_raised {set2(1).reload.check_subtree}
207
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
208
+ end
209
+
210
+ def test_destroy_3
211
+ assert set2(3).destroy
212
+ assert_equal(2, set2(1).children.size)
213
+ assert_equal(0, NestedSetWithStringScope.find(:all, :conditions => "id > 104 and id < 108").size)
214
+ assert_equal(6, set2(1).full_set.size)
215
+ assert_equal(3, set2(2).rgt)
216
+ assert_equal(4, set2(4).lft)
217
+ assert_equal(12, set2(1).rgt)
218
+ assert_nothing_raised {set2(1).check_subtree}
219
+ end
220
+
221
+ def test_destroy_root
222
+ NestedSetWithStringScope.find(4001).destroy
223
+ assert_equal(0, NestedSetWithStringScope.count(:conditions => "root_id = 42"))
224
+ end
225
+
226
+ def test_destroy_dependent
227
+ NestedSetWithStringScope.acts_as_nested_set_options[:dependent] = :destroy
228
+
229
+ big_tree = NestedSetWithStringScope.find(4001)
230
+
231
+ # Make sure we have the right one
232
+ assert_equal(3, big_tree.direct_children.length)
233
+ assert_equal(10, big_tree.full_set.length)
234
+
235
+ NestedSetWithStringScope.find(4005).destroy
236
+
237
+ big_tree = NestedSetWithStringScope.find(4001)
238
+
239
+ assert_equal(7, big_tree.full_set.length)
240
+ assert_equal(2, big_tree.direct_children.length)
241
+
242
+ assert_equal(1, NestedSetWithStringScope.find(4001).lft)
243
+ assert_equal(2, NestedSetWithStringScope.find(4002).lft)
244
+ assert_equal(3, NestedSetWithStringScope.find(4003).lft)
245
+ assert_equal(4, NestedSetWithStringScope.find(4003).rgt)
246
+ assert_equal(5, NestedSetWithStringScope.find(4004).lft)
247
+ assert_equal(6, NestedSetWithStringScope.find(4004).rgt)
248
+ assert_equal(7, NestedSetWithStringScope.find(4002).rgt)
249
+ assert_equal(8, NestedSetWithStringScope.find(4008).lft)
250
+ assert_equal(9, NestedSetWithStringScope.find(4009).lft)
251
+ assert_equal(10, NestedSetWithStringScope.find(4009).rgt)
252
+ assert_equal(11, NestedSetWithStringScope.find(4010).lft)
253
+ assert_equal(12, NestedSetWithStringScope.find(4010).rgt)
254
+ assert_equal(13, NestedSetWithStringScope.find(4008).rgt)
255
+ assert_equal(14, NestedSetWithStringScope.find(4001).rgt)
256
+ end
257
+
258
+ ##########################################
259
+ # QUERY METHOD TESTS
260
+ ##########################################
261
+ def set(id) NestedSet.find(3000 + id) end # helper method
262
+
263
+ def set2(id) NestedSetWithStringScope.find(100 + id) end # helper method
264
+
265
+ def test_root?
266
+ assert NestedSetWithStringScope.find(4001).root?
267
+ assert !NestedSetWithStringScope.find(4002).root?
268
+ end
269
+
270
+ def test_child?
271
+ assert !NestedSetWithStringScope.find(4001).child?
272
+ assert NestedSetWithStringScope.find(4002).child?
273
+ end
274
+
275
+ # Deprecated, delete this test when we nuke the method
276
+ def test_unknown?
277
+ assert !NestedSetWithStringScope.find(4001).unknown?
278
+ assert !NestedSetWithStringScope.find(4002).unknown?
279
+ end
280
+
281
+ # Test the <=> method implicitly
282
+ def test_comparison
283
+ ar = NestedSetWithStringScope.find(:all, :conditions => "root_id = 42", :order => "lft")
284
+ ar2 = NestedSetWithStringScope.find(:all, :conditions => "root_id = 42", :order => "rgt")
285
+ assert_not_equal(ar, ar2)
286
+ assert_equal(ar, ar2.sort)
287
+ end
288
+
289
+ def test_root
290
+ assert_equal(NestedSetWithStringScope.find(4001), NestedSetWithStringScope.find(4007).root)
291
+ assert_equal(set2(1), set2(8).root)
292
+ assert_equal(set2(1), set2(1).root)
293
+ # test virtual roots
294
+ c1, c2, c3 = Category.create, Category.create, Category.create
295
+ c3.move_to_child_of(c2)
296
+ assert_equal(c2, c3.root)
297
+ end
298
+
299
+ def test_roots
300
+ assert_equal([set2(1)], set2(8).roots)
301
+ assert_equal([set2(1)], set2(1).roots)
302
+ assert_equal(NestedSet.find(:all, :conditions => "id > 3000 AND id < 4000").size, set(1).roots.size)
303
+ end
304
+
305
+ def test_parent
306
+ ns = NestedSetWithStringScope.create(:root_id => 45)
307
+ assert_equal(nil, ns.parent)
308
+ assert ns.save
309
+ assert_equal(nil, ns.parent)
310
+ assert_equal(set2(1), set2(2).parent)
311
+ assert_equal(set2(3), set2(7).parent)
312
+ end
313
+
314
+ def test_ancestors
315
+ assert_equal([], set2(1).ancestors)
316
+ assert_equal([set2(1), set2(4), set2(9)], set2(10).ancestors)
317
+ end
318
+
319
+ def test_self_and_ancestors
320
+ assert_equal([set2(1)], set2(1).self_and_ancestors)
321
+ assert_equal([set2(1), set2(4), set2(8)], set2(8).self_and_ancestors)
322
+ assert_equal([set2(1), set2(4), set2(9), set2(10)], set2(10).self_and_ancestors)
323
+ end
324
+
325
+ def test_siblings
326
+ assert_equal([], set2(1).siblings)
327
+ assert_equal([set2(2), set2(4)], set2(3).siblings)
328
+ end
329
+
330
+ def test_first_sibling
331
+ assert set2(2).first_sibling?
332
+ assert_equal(set2(2), set2(2).first_sibling)
333
+ assert_equal(set2(2), set2(3).first_sibling)
334
+ assert_equal(set2(2), set2(4).first_sibling)
335
+ end
336
+
337
+ def test_last_sibling
338
+ assert set2(4).last_sibling?
339
+ assert_equal(set2(4), set2(2).last_sibling)
340
+ assert_equal(set2(4), set2(3).last_sibling)
341
+ assert_equal(set2(4), set2(4).last_sibling)
342
+ end
343
+
344
+ def test_previous_siblings
345
+ assert_equal([], set2(2).previous_siblings)
346
+ assert_equal([set2(2)], set2(3).previous_siblings)
347
+ assert_equal([set2(3), set2(2)], set2(4).previous_siblings)
348
+ end
349
+
350
+ def test_previous_sibling
351
+ assert_equal(nil, set2(2).previous_sibling)
352
+ assert_equal(set2(2), set2(3).previous_sibling)
353
+ assert_equal(set2(3), set2(4).previous_sibling)
354
+ assert_equal([set2(3), set2(2)], set2(4).previous_sibling(2))
355
+ end
356
+
357
+ def test_next_siblings
358
+ assert_equal([], set2(4).next_siblings)
359
+ assert_equal([set2(4)], set2(3).next_siblings)
360
+ assert_equal([set2(3), set2(4)], set2(2).next_siblings)
361
+ end
362
+
363
+ def test_next_sibling
364
+ assert_equal(nil, set2(4).next_sibling)
365
+ assert_equal(set2(4), set2(3).next_sibling)
366
+ assert_equal(set2(3), set2(2).next_sibling)
367
+ assert_equal([set2(3), set2(4)], set2(2).next_sibling(2))
368
+ end
369
+
370
+ def test_self_and_siblings
371
+ assert_equal([set2(1)], set2(1).self_and_siblings)
372
+ assert_equal([set2(2), set2(3), set2(4)], set2(3).self_and_siblings)
373
+ end
374
+
375
+ def test_level
376
+ assert_equal(0, set2(1).level)
377
+ assert_equal(1, set2(3).level)
378
+ assert_equal(3, set2(10).level)
379
+ end
380
+
381
+ def test_all_children_count
382
+ assert_equal(0, set2(10).all_children_count)
383
+ assert_equal(1, set2(3).level)
384
+ assert_equal(3, set2(10).level)
385
+ end
386
+
387
+ def test_full_set
388
+ assert_equal(NestedSetWithStringScope.find(:all, :conditions => "root_id = 101", :order => "lft"), set2(1).full_set)
389
+ new_ns = NestedSetWithStringScope.new(:root_id => 101)
390
+ assert_equal([new_ns], new_ns.full_set)
391
+ assert_equal([set2(4), set2(8), set2(9), set2(10)], set2(4).full_set)
392
+ assert_equal([set2(2)], set2(2).full_set)
393
+ assert_equal([set2(2)], set2(2).full_set(:exclude => nil))
394
+ assert_equal([set2(2)], set2(2).full_set(:exclude => []))
395
+ assert_equal([], set2(1).full_set(:exclude => 101))
396
+ assert_equal([], set2(1).full_set(:exclude => set2(1)))
397
+ ns = NestedSetWithStringScope.create(:root_id => 234)
398
+ assert_equal([], ns.full_set(:exclude => ns))
399
+ assert_equal([set2(4), set2(8), set2(9)], set2(4).full_set(:exclude => set2(10)))
400
+ assert_equal([set2(4), set2(8)], set2(4).full_set(:exclude => set2(9)))
401
+ end
402
+
403
+ def test_all_children
404
+ assert_equal(NestedSetWithStringScope.find(:all, :conditions => "root_id = 101 AND id > 101", :order => "lft"), set2(1).all_children)
405
+ assert_equal([], NestedSetWithStringScope.new(:root_id => 101).all_children)
406
+ assert_equal([set2(8), set2(9), set2(10)], set2(4).all_children)
407
+ assert_equal([set2(8), set2(9)], set2(4).all_children(:exclude => set2(10)))
408
+ assert_equal([set2(8)], set2(4).all_children(:exclude => set2(9)))
409
+ assert_equal([set2(2), set2(4), set2(8)], set2(1).all_children(:exclude => [set2(9), 103]))
410
+ assert_equal([set2(2), set2(4), set2(8)], set2(1).all_children(:exclude => [set2(9), 103, 106]))
411
+ end
412
+
413
+ def test_children
414
+ assert_equal([], set2(10).children)
415
+ assert_equal([], set(1).children)
416
+ assert_equal([set2(2), set2(3), set2(4)], set2(1).children)
417
+ assert_equal([set2(5), set2(6), set2(7)], set2(3).children)
418
+ assert_equal([NestedSetWithStringScope.find(4006), NestedSetWithStringScope.find(4007)], NestedSetWithStringScope.find(4005).children)
419
+ end
420
+
421
+ def test_children_count
422
+ assert_equal(0, set2(10).children_count)
423
+ assert_equal(3, set2(1).children_count)
424
+ end
425
+
426
+ def test_leaves
427
+ assert_equal([set2(10)], set2(9).leaves)
428
+ assert_equal([set2(10)], set2(10).leaves)
429
+ assert_equal([set2(2), set2(5), set2(6), set2(7), set2(8), set2(10)], set2(1).leaves)
430
+ end
431
+
432
+ def test_leaves_count
433
+ assert_equal(1, set2(10).leaves_count)
434
+ assert_equal(1, set2(9).leaves_count)
435
+ assert_equal(6, set2(1).leaves_count)
436
+ end
437
+
438
+ ##########################################
439
+ # CASTING RESULT TESTS
440
+ ##########################################
441
+
442
+ def test_recurse_result_set
443
+ result = []
444
+ NestedSetWithStringScope.recurse_result_set(set2(1).full_set) do |node, level|
445
+ result << [level, node.id]
446
+ end
447
+ expected = [[0, 101], [1, 102], [1, 103], [2, 105], [2, 106], [2, 107], [1, 104], [2, 108], [2, 109], [3, 110]]
448
+ assert_equal expected, result
449
+ end
450
+
451
+ def test_disjointed_result_set
452
+ result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
453
+ result = []
454
+ NestedSetWithStringScope.recurse_result_set(result_set) do |node, level|
455
+ result << [level, node.id]
456
+ end
457
+ expected = [[0, 102], [0, 104], [0, 105], [0, 106], [0, 107], [0, 110]]
458
+ assert_equal expected, result
459
+ end
460
+
461
+ def test_result_to_array
462
+ result = NestedSetWithStringScope.result_to_array(set2(1).full_set) do |node, level|
463
+ { :id => node.id, :level => level }
464
+ end
465
+ expected = [{:level=>0, :children=>[{:level=>1, :id=>102}, {:level=>1,
466
+ :children=>[{:level=>2, :id=>105}, {:level=>2, :id=>106}, {:level=>2, :id=>107}], :id=>103}, {:level=>1,
467
+ :children=>[{:level=>2, :id=>108}, {:level=>2, :children=>[{:level=>3, :id=>110}], :id=>109}], :id=>104}], :id=>101}]
468
+ assert_equal expected, result
469
+ end
470
+
471
+ def test_result_to_array_with_method_calls
472
+ result = NestedSetWithStringScope.result_to_array(set2(1).full_set, :only => [:id], :methods => [:children_count])
473
+ expected = [{:children=>[{:children_count=>0, :id=>102}, {:children=>[{:children_count=>0, :id=>105}, {:children_count=>0, :id=>106},
474
+ {:children_count=>0, :id=>107}], :children_count=>3, :id=>103}, {:children=>[{:children_count=>0, :id=>108}, {:children=>[{:children_count=>0, :id=>110}],
475
+ :children_count=>1, :id=>109}], :children_count=>2, :id=>104}], :children_count=>3, :id=>101}]
476
+ assert_equal expected, result
477
+ end
478
+
479
+ def test_disjointed_result_to_array
480
+ result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
481
+ result = NestedSetWithStringScope.result_to_array(result_set) do |node, level|
482
+ { :id => node.id, :level => level }
483
+ end
484
+ expected = [{:level=>0, :id=>102}, {:level=>0, :id=>104}, {:level=>0, :id=>105}, {:level=>0, :id=>106}, {:level=>0, :id=>107}, {:level=>0, :id=>110}]
485
+ assert_equal expected, result
486
+ end
487
+
488
+ def test_result_to_array_flat
489
+ result = NestedSetWithStringScope.result_to_array(set2(1).full_set, :nested => false) do |node, level|
490
+ { :id => node.id, :level => level }
491
+ end
492
+ expected = [{:level=>0, :id=>101}, {:level=>0, :id=>103}, {:level=>0, :id=>106}, {:level=>0, :id=>104}, {:level=>0, :id=>109},
493
+ {:level=>0, :id=>102}, {:level=>0, :id=>105}, {:level=>0, :id=>107}, {:level=>0, :id=>108}, {:level=>0, :id=>110}]
494
+ assert_equal expected, result
495
+ end
496
+
497
+ def test_result_to_xml
498
+ result = NestedSetWithStringScope.result_to_xml(set2(3).full_set, :record => 'node', :dasherize => false, :only => [:id]) do |options, subnode|
499
+ options[:builder].tag!('type', subnode[:type])
500
+ end
501
+ expected = '<?xml version="1.0" encoding="UTF-8"?>
502
+ <nodes>
503
+ <node>
504
+ <id type="integer">103</id>
505
+ <type>NS2</type>
506
+ <children>
507
+ <node>
508
+ <id type="integer">105</id>
509
+ <type>NestedSetWithStringScope</type>
510
+ <children>
511
+ </children>
512
+ </node>
513
+ <node>
514
+ <id type="integer">106</id>
515
+ <type>NestedSetWithStringScope</type>
516
+ <children>
517
+ </children>
518
+ </node>
519
+ <node>
520
+ <id type="integer">107</id>
521
+ <type>NestedSetWithStringScope</type>
522
+ <children>
523
+ </children>
524
+ </node>
525
+ </children>
526
+ </node>
527
+ </nodes>'
528
+ assert_equal expected, result.strip
529
+ end
530
+
531
+ def test_disjointed_result_to_xml
532
+ result_set = set2(1).full_set(:conditions => ['type IN(?)', ['NestedSetWithStringScope', 'NS2']])
533
+ result = NestedSetWithStringScope.result_to_xml(result_set, :only => [:id])
534
+ # note how nesting is preserved where possible; this is not always what you want though,
535
+ # so you can force a flattened set with :nested => false instead (see below)
536
+ expected = '<?xml version="1.0" encoding="UTF-8"?>
537
+ <nodes>
538
+ <nested-set-with-string-scope>
539
+ <id type="integer">102</id>
540
+ <children>
541
+ </children>
542
+ </nested-set-with-string-scope>
543
+ <ns2>
544
+ <id type="integer">103</id>
545
+ <children>
546
+ <nested-set-with-string-scope>
547
+ <id type="integer">105</id>
548
+ <children>
549
+ </children>
550
+ </nested-set-with-string-scope>
551
+ <nested-set-with-string-scope>
552
+ <id type="integer">106</id>
553
+ <children>
554
+ </children>
555
+ </nested-set-with-string-scope>
556
+ <nested-set-with-string-scope>
557
+ <id type="integer">107</id>
558
+ <children>
559
+ </children>
560
+ </nested-set-with-string-scope>
561
+ </children>
562
+ </ns2>
563
+ <nested-set-with-string-scope>
564
+ <id type="integer">104</id>
565
+ <children>
566
+ <ns2>
567
+ <id type="integer">108</id>
568
+ <children>
569
+ </children>
570
+ </ns2>
571
+ </children>
572
+ </nested-set-with-string-scope>
573
+ </nodes>'
574
+ assert_equal expected, result.strip
575
+ end
576
+
577
+ def test_result_to_xml_flat
578
+ result = NestedSetWithStringScope.result_to_xml(set2(3).full_set, :record => 'node', :dasherize => false, :only => [:id], :nested => false)
579
+ expected = '<?xml version="1.0" encoding="UTF-8"?>
580
+ <nodes>
581
+ <node>
582
+ <id type="integer">103</id>
583
+ </node>
584
+ <node>
585
+ <id type="integer">105</id>
586
+ </node>
587
+ <node>
588
+ <id type="integer">106</id>
589
+ </node>
590
+ <node>
591
+ <id type="integer">107</id>
592
+ </node>
593
+ </nodes>'
594
+ assert_equal expected, result.strip
595
+ end
596
+
597
+ def test_result_to_attribute_based_xml
598
+ result = NestedSetWithStringScope.result_to_attributes_xml(set2(1).full_set, :record => 'node', :only => [:id, :parent_id])
599
+ expected = '<?xml version="1.0" encoding="UTF-8"?>
600
+ <node id="101" parent_id="0">
601
+ <node id="102" parent_id="101"/>
602
+ <node id="103" parent_id="101">
603
+ <node id="105" parent_id="103"/>
604
+ <node id="106" parent_id="103"/>
605
+ <node id="107" parent_id="103"/>
606
+ </node>
607
+ <node id="104" parent_id="101">
608
+ <node id="108" parent_id="104"/>
609
+ <node id="109" parent_id="104">
610
+ <node id="110" parent_id="109"/>
611
+ </node>
612
+ </node>
613
+ </node>'
614
+ assert_equal expected, result.strip
615
+ end
616
+
617
+ def test_result_to_attribute_based_xml_flat
618
+ result = NestedSetWithStringScope.result_to_attributes_xml(set2(1).full_set, :only => [:id], :nested => false, :skip_instruct => true)
619
+ expected = '<ns1 id="101"/>
620
+ <ns2 id="103"/>
621
+ <nested_set_with_string_scope id="106"/>
622
+ <nested_set_with_string_scope id="104"/>
623
+ <ns1 id="109"/>
624
+ <nested_set_with_string_scope id="102"/>
625
+ <nested_set_with_string_scope id="105"/>
626
+ <nested_set_with_string_scope id="107"/>
627
+ <ns2 id="108"/>
628
+ <nested_set_with_string_scope id="110"/>'
629
+ assert_equal expected, result.strip
630
+ end
631
+
632
+ ##########################################
633
+ # WITH_SCOPE QUERY TESTS
634
+ ##########################################
635
+
636
+ def test_filtered_full_set
637
+ result_set = set2(1).full_set(:conditions => { :type => 'NestedSetWithStringScope' })
638
+ assert_equal [102, 105, 106, 107, 104, 110], result_set.map(&:id)
639
+ end
640
+
641
+ def test_reverse_result_set
642
+ result_set = set2(1).full_set(:reverse => true)
643
+ assert_equal [101, 104, 109, 110, 108, 103, 107, 106, 105, 102], result_set.map(&:id)
644
+ # NestedSetWithStringScope.recurse_result_set(result_set) { |node, level| puts "#{'--' * level}#{node.id}" }
645
+ end
646
+
647
+ def test_reordered_full_set
648
+ result_set = set2(1).full_set(:order => 'id DESC')
649
+ assert_equal [110, 109, 108, 107, 106, 105, 104, 103, 102, 101], result_set.map(&:id)
650
+ end
651
+
652
+ def test_filtered_siblings
653
+ node = set2(2)
654
+ result_set = node.siblings(:conditions => { :type => node[:type] })
655
+ assert_equal [104], result_set.map(&:id)
656
+ end
657
+
658
+ def test_include_option_with_full_set
659
+ result_set = set2(3).full_set(:include => :parent_node)
660
+ assert_equal [[103, 101], [105, 103], [106, 103], [107, 103]], result_set.map { |n| [n.id, n.parent_node.id] }
661
+ end
662
+
663
+ ##########################################
664
+ # FIND UNTIL/THROUGH METHOD TESTS
665
+ ##########################################
666
+
667
+ def test_ancestors_and_self_through
668
+ result = set2(10).ancestors_and_self_through(set2(4))
669
+ assert_equal [104, 109, 110], result.map(&:id)
670
+ result = set2(10).ancestors_through(set2(4))
671
+ assert_equal [104, 109], result.map(&:id)
672
+ end
673
+
674
+ def test_full_set_through
675
+ result = set2(4).full_set_through(set2(10))
676
+ assert_equal [104, 108, 109, 110], result.map(&:id)
677
+ end
678
+
679
+ def test_all_children_through
680
+ result = set2(4).all_children_through(set2(10))
681
+ assert_equal [108, 109, 110], result.map(&:id)
682
+ end
683
+
684
+ def test_siblings_through
685
+ result = set2(5).self_and_siblings_through(set2(7))
686
+ assert_equal [105, 106, 107], result.map(&:id)
687
+ result = set2(7).siblings_through(set2(5))
688
+ assert_equal [105, 106], result.map(&:id)
689
+ end
690
+
691
+ ##########################################
692
+ # FIND CHILD BY ID METHOD TESTS
693
+ ##########################################
694
+
695
+ def test_child_by_id
696
+ assert_equal set2(6), set2(3).child_by_id(set2(6).id)
697
+ assert_nil set2(3).child_by_id(set2(8).id)
698
+ end
699
+
700
+ def test_child_of
701
+ assert set2(6).child_of?(set2(3))
702
+ assert !set2(8).child_of?(set2(3))
703
+ assert set2(6).child_of?(set2(3), :conditions => '1 = 1')
704
+ end
705
+
706
+ def test_direct_child_by_id
707
+ assert_equal set2(9), set2(4).direct_child_by_id(set2(9).id)
708
+ assert_nil set2(4).direct_child_by_id(set2(10).id)
709
+ end
710
+
711
+ def test_direct_child_of
712
+ assert set2(9).direct_child_of?(set2(4))
713
+ assert !set2(10).direct_child_of?(set2(4))
714
+ assert set2(9).direct_child_of?(set2(4), :conditions => '1 = 1')
715
+ end
716
+
717
+ ##########################################
718
+ # INDEX-CHECKING METHOD TESTS
719
+ ##########################################
720
+ def test_check_subtree
721
+ root = set2(1)
722
+ assert_nothing_raised {root.check_subtree}
723
+ # need to use update_all to get around attr_protected
724
+ NestedSetWithStringScope.update_all("rgt = #{root.lft + 1}", "id = #{root.id}")
725
+ assert_raise(ActiveRecord::ActiveRecordError) {root.reload.check_subtree}
726
+ assert_nothing_raised {set2(4).check_subtree}
727
+ NestedSetWithStringScope.update_all("lft = 17", "id = 110")
728
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(4).reload.check_subtree}
729
+ NestedSetWithStringScope.update_all("rgt = 18", "id = 110")
730
+ assert_nothing_raised {set2(10).check_subtree}
731
+ NestedSetWithStringScope.update_all("rgt = NULL", "id = 4002")
732
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.find(4001).reload.check_subtree}
733
+ # this method receives lots of additional testing through tests of check_full_tree and check_all
734
+ end
735
+
736
+ def test_check_full_tree
737
+ assert_nothing_raised {set2(1).check_full_tree}
738
+ assert_nothing_raised {NestedSetWithStringScope.find(4006).check_full_tree}
739
+ NestedSetWithStringScope.update_all("rgt = NULL", "id = 4002")
740
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.find(4006).check_full_tree}
741
+ NestedSetWithStringScope.update_all("rgt = 0", "id = 4001")
742
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.find(4006).check_full_tree}
743
+ NestedSetWithStringScope.update_all("rgt = rgt + 1", "id > 101")
744
+ NestedSetWithStringScope.update_all("lft = lft + 1", "id > 101")
745
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(4).check_full_tree}
746
+ end
747
+
748
+ def test_check_full_tree_orphan
749
+ assert_raise(ActiveRecord::RecordNotFound) {NestedSetWithStringScope.find(99)} # make sure ID 99 doesn't exist
750
+ ns = NestedSetWithStringScope.create(:root_id => 101)
751
+ NestedSetWithStringScope.update_all("parent_id = 99", "id = #{ns.id}")
752
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(3).check_full_tree}
753
+ end
754
+
755
+ def test_check_full_tree_endless_loop
756
+ ns = NestedSetWithStringScope.create(:root_id => 101)
757
+ NestedSetWithStringScope.update_all("parent_id = #{ns.id}", "id = #{ns.id}")
758
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(6).check_full_tree}
759
+ end
760
+
761
+ def test_check_full_tree_virtual_roots
762
+ a = Category.create
763
+ b = Category.create
764
+
765
+ assert_nothing_raised {a.check_full_tree}
766
+ Category.update_all("rgt = rgt + 2, lft = lft + 2", "id = #{b.id}") # create a gap between virtual roots
767
+ assert_raise(ActiveRecord::ActiveRecordError) {a.check_full_tree}
768
+ end
769
+
770
+ # see also the tests of check_all under 'class method tests'
771
+
772
+ ##########################################
773
+ # INDEX-ALTERING (UPDATE) METHOD TESTS
774
+ ##########################################
775
+ def test_move_to_left_of # this method undergoes additional testing elsewhere
776
+ set2(2).move_to_left_of(set2(3)) # should cause no change
777
+ assert_equal(2, set2(2).lft)
778
+ assert_equal(4, set2(3).lft)
779
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
780
+ set2(3).move_to_left_of(set2(2))
781
+ assert_equal(9, set2(3).rgt)
782
+ set2(2).move_to_left_of(set2(3))
783
+ assert_equal(2, set2(2).lft)
784
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
785
+ set2(3).move_to_left_of(102) # pass an ID instead
786
+ assert_equal(2, set2(3).lft)
787
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
788
+ end
789
+
790
+ def test_move_to_right_of # this method undergoes additional testing elsewhere
791
+ set2(3).move_to_right_of(set2(2)) # should cause no change
792
+ set2(4).move_to_right_of(set2(3)) # should cause no change
793
+ assert_equal(11, set2(3).rgt)
794
+ assert_equal(19, set2(4).rgt)
795
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
796
+ set2(3).move_to_right_of(set2(4))
797
+ assert_equal(19, set2(3).rgt)
798
+ set2(4).move_to_right_of(set2(3))
799
+ assert_equal(4, set2(3).lft)
800
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
801
+ set2(3).move_to_right_of(104) # pass an ID instead
802
+ assert_equal(4, set2(4).lft)
803
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
804
+ end
805
+
806
+ def test_adding_children
807
+ assert(set(1).unknown?)
808
+ assert(set(2).unknown?)
809
+ set(1).add_child set(2)
810
+
811
+ # Did we maintain adding the parent_ids?
812
+ assert(set(1).root?)
813
+ assert(set(2).child?)
814
+ assert(set(2).parent_id == set(1).id)
815
+
816
+ # Check boundaries
817
+ assert_equal(set(1).lft, 1)
818
+ assert_equal(set(2).lft, 2)
819
+ assert_equal(set(2).rgt, 3)
820
+ assert_equal(set(1).rgt, 4)
821
+
822
+ # Check children cound
823
+ assert_equal(set(1).all_children_count, 1)
824
+
825
+ set(1).add_child set(3)
826
+
827
+ #check boundries
828
+ assert_equal(set(1).lft, 1)
829
+ assert_equal(set(2).lft, 2)
830
+ assert_equal(set(2).rgt, 3)
831
+ assert_equal(set(3).lft, 4)
832
+ assert_equal(set(3).rgt, 5)
833
+ assert_equal(set(1).rgt, 6)
834
+
835
+ # How is the count looking?
836
+ assert_equal(set(1).all_children_count, 2)
837
+
838
+ set(2).add_child set(4)
839
+
840
+ # boundries
841
+ assert_equal(set(1).lft, 1)
842
+ assert_equal(set(2).lft, 2)
843
+ assert_equal(set(4).lft, 3)
844
+ assert_equal(set(4).rgt, 4)
845
+ assert_equal(set(2).rgt, 5)
846
+ assert_equal(set(3).lft, 6)
847
+ assert_equal(set(3).rgt, 7)
848
+ assert_equal(set(1).rgt, 8)
849
+
850
+ # Children count
851
+ assert_equal(set(1).all_children_count, 3)
852
+ assert_equal(set(2).all_children_count, 1)
853
+ assert_equal(set(3).all_children_count, 0)
854
+ assert_equal(set(4).all_children_count, 0)
855
+
856
+ set(2).add_child set(5)
857
+ set(4).add_child set(6)
858
+
859
+ assert_equal(set(2).all_children_count, 3)
860
+
861
+ # Children accessors
862
+ assert_equal(set(1).full_set.length, 6)
863
+ assert_equal(set(2).full_set.length, 4)
864
+ assert_equal(set(4).full_set.length, 2)
865
+
866
+ assert_equal(set(1).all_children.length, 5)
867
+ assert_equal(set(6).all_children.length, 0)
868
+
869
+ assert_equal(set(1).direct_children.length, 2)
870
+
871
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
872
+ end
873
+
874
+ def test_common_usage
875
+ mixins(:set_1).add_child(mixins(:set_2))
876
+ assert_equal(1, mixins(:set_1).direct_children.length)
877
+
878
+ mixins(:set_2).add_child(mixins(:set_3))
879
+ assert_equal(1, mixins(:set_1).direct_children.length)
880
+
881
+ # Local cache is now out of date!
882
+ # Problem: the update_alls update all objects up the tree
883
+ mixins(:set_1).reload
884
+ assert_equal(2, mixins(:set_1).all_children.length)
885
+
886
+ assert_equal(1, mixins(:set_1).lft)
887
+ assert_equal(2, mixins(:set_2).lft)
888
+ assert_equal(3, mixins(:set_3).lft)
889
+ assert_equal(4, mixins(:set_3).rgt)
890
+ assert_equal(5, mixins(:set_2).rgt)
891
+ assert_equal(6, mixins(:set_1).rgt)
892
+ assert(mixins(:set_1).root?)
893
+
894
+ begin
895
+ mixins(:set_4).add_child(mixins(:set_1))
896
+ fail
897
+ rescue
898
+ end
899
+
900
+ assert_equal(2, mixins(:set_1).all_children.length)
901
+ mixins(:set_1).add_child mixins(:set_4)
902
+ assert_equal(3, mixins(:set_1).all_children.length)
903
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
904
+ end
905
+
906
+ def test_move_to_child_of_1
907
+ bill = NestedSetWithStringScope.new(:root_id => 101, :pos => 2)
908
+ assert_raise(ActiveRecord::ActiveRecordError) { bill.move_to_child_of(set2(1)) }
909
+ assert_raise(ActiveRecord::ActiveRecordError) { set2(1).move_to_child_of(set2(1)) }
910
+ assert_raise(ActiveRecord::ActiveRecordError) { set2(4).move_to_child_of(set2(9)) }
911
+ assert bill.save
912
+ assert_nothing_raised {set2(1).reload.check_subtree}
913
+ assert bill.move_to_left_of(set2(3))
914
+ assert_equal set2(1), bill.parent
915
+ assert_equal 4, bill.lft
916
+ assert_equal 5, bill.rgt
917
+ assert_equal 3, set2(2).reload.rgt
918
+ assert_equal 6, set2(3).reload.lft
919
+ assert_equal 22, set2(1).reload.rgt
920
+ assert_nothing_raised {set2(1).reload.check_subtree}
921
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
922
+ set2(9).move_to_child_of(101) # pass an ID instead
923
+ assert set2(1).children.include?(set2(9))
924
+ assert_equal(18, set2(9).lft) # to the right of existing children?
925
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
926
+ end
927
+
928
+ def test_move_to_child_of_2
929
+ bill = NestedSetWithStringScope.new(:root_id => 101)
930
+ assert_nothing_raised {set2(1).check_subtree}
931
+ assert bill.save
932
+ assert bill.move_to_child_of(set2(10))
933
+ assert_equal set2(10), bill.parent
934
+ assert_equal 17, bill.lft
935
+ assert_equal 18, bill.rgt
936
+ assert_equal 16, set2(10).reload.lft
937
+ assert_equal 19, set2(10).reload.rgt
938
+ assert_equal 15, set2(9).reload.lft
939
+ assert_equal 20, set2(9).reload.rgt
940
+ assert_equal 21, set2(4).reload.rgt
941
+ assert_nothing_raised {set2(9).reload.check_subtree}
942
+ assert_nothing_raised {set2(4).reload.check_subtree}
943
+ assert_nothing_raised {set2(1).reload.check_subtree}
944
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
945
+ end
946
+
947
+ def test_move_to_child_of_3
948
+ bill = NestedSetWithStringScope.new(:root_id => 101)
949
+ assert bill.save
950
+ assert bill.move_to_child_of(set2(3))
951
+ assert_equal(11, bill.lft) # to the right of existing children?
952
+ assert_nothing_raised {set2(1).reload.check_subtree}
953
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
954
+ end
955
+
956
+ def test_move_1
957
+ set2(4).move_to_child_of(set2(3))
958
+ assert_equal(set2(3), set2(4).reload.parent)
959
+ assert_equal(1, set2(1).reload.lft)
960
+ assert_equal(20, set2(1).reload.rgt)
961
+ assert_equal(4, set2(3).reload.lft)
962
+ assert_equal(19, set2(3).reload.rgt)
963
+ assert_nothing_raised {set2(1).reload.check_subtree}
964
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
965
+ end
966
+
967
+ def test_move_2
968
+ initial = set2(1).full_set
969
+ assert_raise(ActiveRecord::ActiveRecordError) { set2(3).move_to_child_of(set2(6)) } # can't set a current child as the parent-- creates a loop
970
+ assert_raise(ActiveRecord::ActiveRecordError) { set2(3).move_to_child_of(set2(3)) }
971
+ set2(2).move_to_child_of(set2(5))
972
+ set2(4).move_to_child_of(set2(2))
973
+ set2(10).move_to_right_of(set2(3))
974
+
975
+ assert_equal 105, set2(2).parent_id
976
+ assert_equal 102, set2(4).parent_id
977
+ assert_equal 101, set2(10).parent_id
978
+ set2(3).reload
979
+ set2(10).reload
980
+ assert_equal 19, set2(10).rgt
981
+ assert_equal 17, set2(3).rgt
982
+ assert_equal 2, set2(3).lft
983
+ set2(1).reload
984
+ assert_nothing_raised {set2(1).check_subtree}
985
+ set2(4).move_to_right_of(set2(3))
986
+ set2(10).move_to_child_of(set2(9))
987
+ set2(2).move_to_left_of(set2(3))
988
+
989
+ # now everything should be back where it started-- check against initial
990
+ final = set2(1).reload.full_set
991
+ assert_equal(initial, final)
992
+ for i in 0..9
993
+ assert_equal(initial[i]['parent_id'], final[i]['parent_id'])
994
+ assert_equal(initial[i]['lft'], final[i]['lft'])
995
+ assert_equal(initial[i]['rgt'], final[i]['rgt'])
996
+ end
997
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
998
+ end
999
+
1000
+ def test_scope_enforcement # prevent moves between trees
1001
+ assert_raise(ActiveRecord::ActiveRecordError) { set(3).move_to_child_of(set2(6)) }
1002
+ ns = NestedSetWithStringScope.create(:root_id => 214)
1003
+ assert_raise(ActiveRecord::ActiveRecordError) { ns.move_to_child_of(set2(1)) }
1004
+ end
1005
+
1006
+ ##########################################
1007
+ # ACTS_AS_LIST-LIKE BEHAVIOUR TESTS
1008
+ ##########################################
1009
+
1010
+ def test_swap
1011
+ set2(5).swap(set2(7))
1012
+ assert_equal [107, 106, 105], set2(3).children.map(&:id)
1013
+ assert_nothing_raised {set2(3).check_full_tree}
1014
+ assert_raise(ActiveRecord::ActiveRecordError) { set2(3).swap(set2(10)) } # isn't a sibling...
1015
+ end
1016
+
1017
+ def test_insert_at
1018
+ child = NestedSetWithStringScope.create(:root_id => 101)
1019
+ child.insert_at(set2(3), :last)
1020
+ assert_equal child, set2(3).children.last
1021
+
1022
+ child = NestedSetWithStringScope.create(:root_id => 101)
1023
+ child.insert_at(set2(3), :first)
1024
+ assert_equal child, set2(3).children.first
1025
+
1026
+ child = NestedSetWithStringScope.create(:root_id => 101)
1027
+ child.insert_at(set2(3), 2)
1028
+ assert_equal child, set2(3).children[2]
1029
+
1030
+ child = NestedSetWithStringScope.create(:root_id => 101)
1031
+ child.insert_at(set2(3), 1000)
1032
+ assert_equal child, set2(3).children.last
1033
+
1034
+ child = NestedSetWithStringScope.create(:root_id => 101)
1035
+ child.insert_at(set2(3), 1)
1036
+ assert_equal child, set2(3).children[1]
1037
+ end
1038
+
1039
+ def test_move_higher
1040
+ set2(7).move_higher
1041
+ assert_equal [105, 107, 106], set2(3).children.map(&:id)
1042
+ set2(7).move_higher
1043
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1044
+ set2(7).move_higher
1045
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1046
+ end
1047
+
1048
+ def test_move_lower
1049
+ set2(5).move_lower
1050
+ assert_equal [106, 105, 107], set2(3).children.map(&:id)
1051
+ set2(5).move_lower
1052
+ assert_equal [106, 107, 105], set2(3).children.map(&:id)
1053
+ set2(5).move_lower
1054
+ assert_equal [106, 107, 105], set2(3).children.map(&:id)
1055
+ end
1056
+
1057
+ def test_move_to_top
1058
+ set2(7).move_to_top
1059
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1060
+ end
1061
+
1062
+ def test_move_to_bottom
1063
+ set2(5).move_to_bottom
1064
+ assert_equal [106, 107, 105], set2(3).children.map(&:id)
1065
+ end
1066
+
1067
+ def test_move_to_position
1068
+ set2(7).move_to_position(:first)
1069
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1070
+ set2(7).move_to_position(:last)
1071
+ assert_equal [105, 106, 107], set2(3).children.map(&:id)
1072
+ end
1073
+
1074
+ def test_move_to_position_limits
1075
+ set2(7).move_to_position(0)
1076
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1077
+ set2(7).move_to_position(100)
1078
+ assert_equal [105, 106, 107], set2(3).children.map(&:id)
1079
+ end
1080
+
1081
+ def test_move_to_position_index
1082
+ set2(7).move_to_position(0)
1083
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1084
+ set2(7).move_to_position(1)
1085
+ assert_equal [105, 107, 106], set2(3).children.map(&:id)
1086
+ set2(7).move_to_position(2)
1087
+ assert_equal [105, 106, 107], set2(3).children.map(&:id)
1088
+ set2(5).move_to_position(2)
1089
+ assert_equal [106, 107, 105], set2(3).children.map(&:id)
1090
+ end
1091
+
1092
+ def test_scoped_move_to_position
1093
+ set2(7).move_to_position(0, :conditions => { :id => [105, 106, 107] })
1094
+ assert_equal [107, 105, 106], set2(3).children.map(&:id)
1095
+ set2(7).move_to_position(1, :conditions => { :id => [105, 107] })
1096
+ assert_equal [105, 107, 106], set2(3).children.map(&:id)
1097
+ set2(7).move_to_position(1, :conditions => { :id => [106, 107] })
1098
+ assert_equal [105, 106, 107], set2(3).children.map(&:id)
1099
+ end
1100
+
1101
+ def test_reorder_children
1102
+ assert_equal [105], set2(3).reorder_children(107, 106, 105).map(&:id)
1103
+ assert_equal [107, 106, 105], set2(3).children.map(&:id)
1104
+ assert_equal [107, 106], set2(3).reorder_children(106, 105, 107).map(&:id)
1105
+ assert_equal [106, 105, 107], set2(3).children.map(&:id)
1106
+ end
1107
+
1108
+ def test_reorder_children_with_random_samples
1109
+ 10.times do
1110
+ child = NestedSetWithStringScope.create(:root_id => 101)
1111
+ child.move_to_child_of set2(3)
1112
+ end
1113
+ ordered_ids = set2(3).children.map(&:id).sort_by { rand }
1114
+ set2(3).reorder_children(ordered_ids)
1115
+ assert_equal ordered_ids, set2(3).children.map(&:id)
1116
+ end
1117
+
1118
+ def test_reorder_children_with_partial_id_set
1119
+ 10.times do
1120
+ child = NestedSetWithStringScope.create(:root_id => 101)
1121
+ child.move_to_child_of set2(3)
1122
+ end
1123
+ child_ids = set2(3).children.map(&:id)
1124
+ set2(3).reorder_children(child_ids.last, child_ids.first)
1125
+ ordered_ids = set2(3).children.map(&:id)
1126
+ assert_equal ordered_ids.first, child_ids.last
1127
+ assert_equal ordered_ids.last, child_ids.first
1128
+ assert_equal child_ids[1, -2], ordered_ids[1, -2]
1129
+ end
1130
+
1131
+ ##########################################
1132
+ # RENUMBERING TESTS
1133
+ ##########################################
1134
+ # see also class method tests of renumber_all
1135
+ def test_renumber_full_tree_1
1136
+ NestedSetWithStringScope.update_all("lft = NULL, rgt = NULL", "root_id = 101")
1137
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(1).check_full_tree}
1138
+ set2(1).renumber_full_tree
1139
+ set2(1).reload
1140
+ assert_equal 1, set2(1).lft
1141
+ assert_equal 20, set2(1).rgt
1142
+ assert_equal 4, set2(3).lft
1143
+ assert_equal 11, set2(3).rgt
1144
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1145
+ end
1146
+
1147
+ def test_renumber_full_tree_2
1148
+ NestedSetWithStringScope.update_all("lft = lft + 1, rgt = rgt + 1", "root_id = 101")
1149
+ assert_raise(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
1150
+ set2(1).renumber_full_tree
1151
+ assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
1152
+ NestedSetWithStringScope.update_all("rgt = 12", "id = 108")
1153
+ assert_raise(ActiveRecord::ActiveRecordError) {set2(8).check_subtree}
1154
+ set2(8).renumber_full_tree
1155
+ assert_nothing_raised(ActiveRecord::ActiveRecordError) {NestedSetWithStringScope.check_all}
1156
+ end
1157
+
1158
+
1159
+ ##########################################
1160
+ # CONCURRENCY TESTS
1161
+ ##########################################
1162
+ # what happens when multiple objects are being manipulated at the same time?
1163
+ def test_concurrent_save
1164
+ c1, c2, c3 = Category.create, Category.create, Category.create
1165
+ c1.move_to_right_of(c3)
1166
+ c2.save
1167
+ assert_nothing_raised {Category.check_all}
1168
+
1169
+ ns1 = set2(3)
1170
+ ns2 = set2(4)
1171
+ ns2.move_to_left_of(102) # ns1 is now out-of-date
1172
+ ns1.save
1173
+ assert_nothing_raised {set2(1).check_subtree}
1174
+ end
1175
+
1176
+ def test_concurrent_add_add
1177
+ c1 = Category.new
1178
+ c2 = Category.new
1179
+ c1.save
1180
+ c2.save
1181
+ c3 = Category.new
1182
+ c4 = Category.new
1183
+ c4.save # now in the opposite order
1184
+ c3.save
1185
+ assert_nothing_raised {Category.check_all}
1186
+ end
1187
+
1188
+ def test_concurrent_add_delete
1189
+ ns = set2(3)
1190
+ new_ns = NestedSetWithStringScope.create(:root_id => 101)
1191
+ ns.destroy
1192
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1193
+ end
1194
+
1195
+ def test_concurrent_add_move
1196
+ ns = set2(3)
1197
+ new_ns = NestedSetWithStringScope.create(:root_id => 101)
1198
+ ns.move_to_left_of(102)
1199
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1200
+ end
1201
+
1202
+ def test_concurrent_delete_add
1203
+ ns = set2(3)
1204
+ new_ns = NestedSetWithStringScope.new(:root_id => 101)
1205
+ ns.destroy
1206
+ new_ns.save
1207
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1208
+ end
1209
+
1210
+ def test_concurrent_delete_delete
1211
+ ns1 = set2(3)
1212
+ ns2 = set2(4)
1213
+ ns1.destroy
1214
+ ns2.destroy
1215
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1216
+ end
1217
+
1218
+ def test_concurrent_delete_move
1219
+ ns1 = set2(3)
1220
+ ns2 = set2(4)
1221
+ ns1.destroy
1222
+ ns2.move_to_left_of(102)
1223
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1224
+ end
1225
+
1226
+ def test_concurrent_move_add
1227
+ ns = set2(3)
1228
+ new_ns = NestedSetWithStringScope.new(:root_id => 101)
1229
+ ns.move_to_left_of(102)
1230
+ new_ns.save
1231
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1232
+ end
1233
+
1234
+ def test_concurrent_move_delete
1235
+ ns1 = set2(3)
1236
+ ns2 = set2(4)
1237
+ ns2.move_to_left_of(102)
1238
+ ns1.destroy
1239
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1240
+ end
1241
+
1242
+ def test_concurrent_move_move
1243
+ ns1 = set2(3)
1244
+ ns2 = set2(4)
1245
+ ns1.move_to_left_of(102)
1246
+ ns2.move_to_child_of(102)
1247
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1248
+ end
1249
+
1250
+ ##########################################
1251
+ # CALLBACK TESTS
1252
+ ##########################################
1253
+ # Because the nested set code relies heavily on callbacks, we
1254
+ # want to ensure that we aren't causing problems for user-defined callbacks
1255
+ def test_callbacks
1256
+ # 1) Do all user-defined callbacks work?
1257
+ $callbacks = []
1258
+ ns = NS2.new(:root_id => 101) # NS2 adds symbols to $callbacks when the callbacks fire
1259
+ assert_equal([], $callbacks)
1260
+ ns.save!
1261
+ assert_equal([:before_save, :before_create, :after_create, :after_save], $callbacks)
1262
+ $callbacks = []
1263
+ ns.pos = 2
1264
+ ns.save!
1265
+ assert_equal([:before_save, :before_update, :after_update, :after_save], $callbacks)
1266
+ $callbacks = []
1267
+ ns.destroy
1268
+ assert_equal([:before_destroy, :after_destroy], $callbacks)
1269
+ end
1270
+
1271
+ def test_callbacks2
1272
+ # 2) Do our callbacks still work, even when a programmer defines
1273
+ # their own callbacks in the overwriteable style?
1274
+ # (the NS2 model defines callbacks in the overwritable style)
1275
+ ns = NS2.create(:root_id => 101)
1276
+ assert ns.lft != nil && ns.rgt != nil
1277
+ child_ns = NS2.create(:root_id => 101)
1278
+ child_ns.move_to_child_of(ns)
1279
+ id = child_ns.id
1280
+ ns.destroy
1281
+ assert_equal(nil, NS2.find(:first, :conditions => "id = #{id}"))
1282
+ # lots of implicit testing occurs in other test methods
1283
+ end
1284
+
1285
+ ##########################################
1286
+ # BUG-SPECIFIC TESTS
1287
+ ##########################################
1288
+ def test_ticket_17
1289
+ main = Category.new
1290
+ main.save
1291
+ sub = Category.new
1292
+ sub.save
1293
+ sub.move_to_child_of main
1294
+ sub.save
1295
+ main.save
1296
+
1297
+ assert_equal(1, main.all_children_count)
1298
+ assert_equal([main, sub], main.full_set)
1299
+ assert_equal([sub], main.all_children)
1300
+
1301
+ assert_equal(1, main.lft)
1302
+ assert_equal(2, sub.lft)
1303
+ assert_equal(3, sub.rgt)
1304
+ assert_equal(4, main.rgt)
1305
+ end
1306
+
1307
+ def test_ticket_19
1308
+ # this test currently relies on the fact that objects are reloaded at the beginning of the move_to methods
1309
+ root = Category.create
1310
+ first = Category.create
1311
+ second = Category.create
1312
+ first.move_to_child_of(root)
1313
+ second.move_to_child_of(root)
1314
+
1315
+ # now we should have the situation described in the ticket
1316
+ assert_nothing_raised {first.move_to_child_of(second)}
1317
+ assert_raise(ActiveRecord::ActiveRecordError) {second.move_to_child_of(first)} # try illegal move
1318
+ first.move_to_child_of(root) # move it back
1319
+ assert_nothing_raised {first.move_to_child_of(second)} # try it the other way-- first is now on the other side of second
1320
+ assert_nothing_raised {Category.check_all}
1321
+ end
1322
+
1323
+ # Note that single-table inheritance recieves extensive implicit testing,
1324
+ # because one of the fixture trees contains a hodge-podge of classes.
1325
+ def test_ticket_10
1326
+ assert_equal(9, set2(1).all_children.size)
1327
+ NS2.find(103).move_to_right_of(104)
1328
+ assert_equal(4, set2(4).lft)
1329
+ assert_equal(10, set2(9).rgt)
1330
+ NS2.find(103).destroy
1331
+ assert_equal(12, set2(1).rgt)
1332
+ assert_equal(6, NestedSetWithStringScope.count(:conditions => "root_id = 101"))
1333
+ assert_nothing_raised {NestedSetWithStringScope.check_all}
1334
+ end
1335
+
1336
+ # the next virtual root was not starting with the correct lft value
1337
+ def test_ticket_29
1338
+ first = Category.create
1339
+ second = Category.create
1340
+ Category.renumber_all
1341
+ second.reload
1342
+ assert_equal(3, second.lft)
1343
+ end
1344
+
1345
+ end
1346
+
1347
+
1348
+
1349
+ ###################################################################
1350
+ ## Tests that don't pass yet or haven't been finished
1351
+
1352
+ ## make #destroy set left & rgt to nil?
1353
+
1354
+ #def test_find_insertion_point
1355
+ # bill = NestedSetWithStringScope.create(:pos => 2, :root_id => 101)
1356
+ # assert_equal 3, bill.find_insertion_point(set2(1))
1357
+ # assert_equal 4, bill.find_insertion_point(set2(3))
1358
+ # aalfred = NestedSetWithStringScope.create(:pos => 0, :root_id => 101)
1359
+ # assert_equal 1, aalfred.find_insertion_point(set2(1))
1360
+ # assert_equal 2, aalfred.find_insertion_point(set2(2))
1361
+ # assert_equal 12, aalfred.find_insertion_point(set2(4))
1362
+ # zed = NestedSetWithStringScope.create(:pos => 99, :root_id => 101)
1363
+ # assert_equal 19, zed.find_insertion_point(set2(1))
1364
+ # assert_equal 17, zed.find_insertion_point(set2(9))
1365
+ # assert_equal 16, zed.find_insertion_point(set2(10))
1366
+ # assert_equal 10, set2(4).find_insertion_point(set2(3))
1367
+ #end
1368
+ #