ggoodale-awesome_nested_set 1.1

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.
@@ -0,0 +1,29 @@
1
+ # Rails <2.x doesn't define #except
2
+ class Hash
3
+ # Returns a new hash without the given keys.
4
+ def except(*keys)
5
+ clone.except!(*keys)
6
+ end unless method_defined?(:except)
7
+
8
+ # Replaces the hash without the given keys.
9
+ def except!(*keys)
10
+ keys.map! { |key| convert_key(key) } if respond_to?(:convert_key)
11
+ keys.each { |key| delete(key) }
12
+ self
13
+ end unless method_defined?(:except!)
14
+ end
15
+
16
+ # NamedScope is new to Rails 2.1
17
+ unless defined? ActiveRecord::NamedScope
18
+ require 'awesome_nested_set/named_scope'
19
+ ActiveRecord::Base.class_eval do
20
+ include CollectiveIdea::NamedScope
21
+ end
22
+ end
23
+
24
+ # Rails 1.2.x doesn't define #quoted_table_name
25
+ class ActiveRecord::Base
26
+ def self.quoted_table_name
27
+ self.connection.quote_column_name(self.table_name)
28
+ end unless methods.include?('quoted_table_name')
29
+ end
@@ -0,0 +1,41 @@
1
+ module CollectiveIdea
2
+ module Acts #:nodoc:
3
+ module NestedSet #:nodoc:
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 Helper
9
+ # Returns options for select.
10
+ # You can exclude some items from the tree.
11
+ # You can pass a block receiving an item and returning the string displayed in the select.
12
+ #
13
+ # == Params
14
+ # * +class_or_item+ - Class name or top level times
15
+ # * +mover+ - The item that is being move, used to exlude impossible moves
16
+ # * +&block+ - a block that will be used to display: { |item| ... item.name }
17
+ #
18
+ # == Usage
19
+ #
20
+ # <%= f.select :parent_id, nested_set_options(Category, @category) {|i|
21
+ # "#{'–' * i.level} #{i.name}"
22
+ # }) %>
23
+ #
24
+ def nested_set_options(class_or_item, mover = nil)
25
+ class_or_item = class_or_item.roots if class_or_item.is_a?(Class)
26
+ items = Array(class_or_item)
27
+ result = []
28
+ items.each do |root|
29
+ result += root.self_and_descendants.map do |i|
30
+ if mover.nil? || mover.new_record? || mover.move_possible?(i)
31
+ [yield(i), i.id]
32
+ end
33
+ end.compact
34
+ end
35
+ result
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,140 @@
1
+ # Taken from Rails 2.1
2
+ module CollectiveIdea
3
+ module NamedScope
4
+ # All subclasses of ActiveRecord::Base have two named_scopes:
5
+ # * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
6
+ # * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly:
7
+ #
8
+ # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
9
+ #
10
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
11
+ # intermediate values (scopes) around as first-class objects is convenient.
12
+ def self.included(base)
13
+ base.class_eval do
14
+ extend ClassMethods
15
+ named_scope :scoped, lambda { |scope| scope }
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def scopes
21
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
22
+ end
23
+
24
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
25
+ # such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
26
+ #
27
+ # class Shirt < ActiveRecord::Base
28
+ # named_scope :red, :conditions => {:color => 'red'}
29
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
30
+ # end
31
+ #
32
+ # The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
33
+ # in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
34
+ #
35
+ # Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
36
+ # constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
37
+ # <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
38
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
39
+ # <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
40
+ #
41
+ # These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
42
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
43
+ # for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
44
+ #
45
+ # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
46
+ # <tt>has_many</tt> associations. If,
47
+ #
48
+ # class Person < ActiveRecord::Base
49
+ # has_many :shirts
50
+ # end
51
+ #
52
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
53
+ # only shirts.
54
+ #
55
+ # Named scopes can also be procedural.
56
+ #
57
+ # class Shirt < ActiveRecord::Base
58
+ # named_scope :colored, lambda { |color|
59
+ # { :conditions => { :color => color } }
60
+ # }
61
+ # end
62
+ #
63
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
64
+ #
65
+ # Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
66
+ #
67
+ # class Shirt < ActiveRecord::Base
68
+ # named_scope :red, :conditions => {:color => 'red'} do
69
+ # def dom_id
70
+ # 'red_shirts'
71
+ # end
72
+ # end
73
+ # end
74
+ #
75
+ #
76
+ # For testing complex named scopes, you can examine the scoping options using the
77
+ # <tt>proxy_options</tt> method on the proxy itself.
78
+ #
79
+ # class Shirt < ActiveRecord::Base
80
+ # named_scope :colored, lambda { |color|
81
+ # { :conditions => { :color => color } }
82
+ # }
83
+ # end
84
+ #
85
+ # expected_options = { :conditions => { :colored => 'red' } }
86
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
87
+ def named_scope(name, options = {}, &block)
88
+ scopes[name] = lambda do |parent_scope, *args|
89
+ Scope.new(parent_scope, case options
90
+ when Hash
91
+ options
92
+ when Proc
93
+ options.call(*args)
94
+ end, &block)
95
+ end
96
+ (class << self; self end).instance_eval do
97
+ define_method name do |*args|
98
+ scopes[name].call(self, *args)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ class Scope
105
+ attr_reader :proxy_scope, :proxy_options
106
+ [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
107
+ delegate :scopes, :with_scope, :to => :proxy_scope
108
+
109
+ def initialize(proxy_scope, options, &block)
110
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
111
+ extend Module.new(&block) if block_given?
112
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
113
+ end
114
+
115
+ def reload
116
+ load_found; self
117
+ end
118
+
119
+ protected
120
+ def proxy_found
121
+ @found || load_found
122
+ end
123
+
124
+ private
125
+ def method_missing(method, *args, &block)
126
+ if scopes.include?(method)
127
+ scopes[method].call(self, *args)
128
+ else
129
+ with_scope :find => proxy_options do
130
+ proxy_scope.send(method, *args, &block)
131
+ end
132
+ end
133
+ end
134
+
135
+ def load_found
136
+ @found = find(:all)
137
+ end
138
+ end
139
+ end
140
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'awesome_nested_set/compatability'
2
+ require 'awesome_nested_set'
3
+
4
+ ActiveRecord::Base.class_eval do
5
+ include CollectiveIdea::Acts::NestedSet
6
+ end
7
+
8
+ if defined?(ActionView)
9
+ require 'awesome_nested_set/helper'
10
+ ActionView::Base.class_eval do
11
+ include CollectiveIdea::Acts::NestedSet::Helper
12
+ end
13
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+
3
+ module CollectiveIdea
4
+ module Acts #:nodoc:
5
+ module NestedSet #:nodoc:
6
+ class AwesomeNestedSetTest < Test::Unit::TestCase
7
+ include Helper
8
+ fixtures :categories
9
+
10
+ def test_nested_set_options
11
+ expected = [
12
+ [" Top Level", 1],
13
+ ["- Child 1", 2],
14
+ ['- Child 2', 3],
15
+ ['-- Child 2.1', 4],
16
+ ['- Child 3', 5],
17
+ [" Top Level 2", 6]
18
+ ]
19
+ actual = nested_set_options(Category) do |c|
20
+ "#{'-' * c.level} #{c.name}"
21
+ end
22
+ assert_equal expected, actual
23
+ end
24
+
25
+ def test_nested_set_options_with_mover
26
+ expected = [
27
+ [" Top Level", 1],
28
+ ["- Child 1", 2],
29
+ ['- Child 3', 5],
30
+ [" Top Level 2", 6]
31
+ ]
32
+ actual = nested_set_options(Category, categories(:child_2)) do |c|
33
+ "#{'-' * c.level} #{c.name}"
34
+ end
35
+ assert_equal expected, actual
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,594 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Note < ActiveRecord::Base
4
+ acts_as_nested_set :scope => [:notable_id, :notable_type]
5
+ end
6
+
7
+ class AwesomeNestedSetTest < Test::Unit::TestCase
8
+
9
+ class Default < ActiveRecord::Base
10
+ acts_as_nested_set
11
+ set_table_name 'categories'
12
+ end
13
+ class Scoped < ActiveRecord::Base
14
+ acts_as_nested_set :scope => :organization
15
+ set_table_name 'categories'
16
+ end
17
+
18
+ def test_left_column_default
19
+ assert_equal 'lft', Default.acts_as_nested_set_options[:left_column]
20
+ end
21
+
22
+ def test_right_column_default
23
+ assert_equal 'rgt', Default.acts_as_nested_set_options[:right_column]
24
+ end
25
+
26
+ def test_parent_column_default
27
+ assert_equal 'parent_id', Default.acts_as_nested_set_options[:parent_column]
28
+ end
29
+
30
+ def test_scope_default
31
+ assert_nil Default.acts_as_nested_set_options[:scope]
32
+ end
33
+
34
+ def test_left_column_name
35
+ assert_equal 'lft', Default.left_column_name
36
+ assert_equal 'lft', Default.new.left_column_name
37
+ end
38
+
39
+ def test_right_column_name
40
+ assert_equal 'rgt', Default.right_column_name
41
+ assert_equal 'rgt', Default.new.right_column_name
42
+ end
43
+
44
+ def test_parent_column_name
45
+ assert_equal 'parent_id', Default.parent_column_name
46
+ assert_equal 'parent_id', Default.new.parent_column_name
47
+ end
48
+
49
+ def test_quoted_left_column_name
50
+ quoted = Default.connection.quote_column_name('lft')
51
+ assert_equal quoted, Default.quoted_left_column_name
52
+ assert_equal quoted, Default.new.quoted_left_column_name
53
+ end
54
+
55
+ def test_quoted_right_column_name
56
+ quoted = Default.connection.quote_column_name('rgt')
57
+ assert_equal quoted, Default.quoted_right_column_name
58
+ assert_equal quoted, Default.new.quoted_right_column_name
59
+ end
60
+
61
+ def test_left_column_protected_from_assignment
62
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.lft = 1 }
63
+ end
64
+
65
+ def test_right_column_protected_from_assignment
66
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.rgt = 1 }
67
+ end
68
+
69
+ def test_parent_column_protected_from_assignment
70
+ assert_raises(ActiveRecord::ActiveRecordError) { Category.new.parent_id = 1 }
71
+ end
72
+
73
+ def test_colums_protected_on_initialize
74
+ c = Category.new(:lft => 1, :rgt => 2, :parent_id => 3)
75
+ assert_nil c.lft
76
+ assert_nil c.rgt
77
+ assert_nil c.parent_id
78
+ end
79
+
80
+ def test_scoped_appends_id
81
+ assert_equal :organization_id, Scoped.acts_as_nested_set_options[:scope]
82
+ end
83
+
84
+ def test_roots_class_method
85
+ assert_equal Category.find_all_by_parent_id(nil), Category.roots
86
+ end
87
+
88
+ def test_root_class_method
89
+ assert_equal categories(:top_level), Category.root
90
+ end
91
+
92
+ def test_root
93
+ assert_equal categories(:top_level), categories(:child_3).root
94
+ end
95
+
96
+ def test_root?
97
+ assert categories(:top_level).root?
98
+ assert categories(:top_level_2).root?
99
+ end
100
+
101
+ def test_leaves_class_method
102
+ assert_equal Category.find(:all, :conditions => "#{Category.right_column_name} - #{Category.left_column_name} = 1"), Category.leaves
103
+ assert_equal Category.leaves.count, 4
104
+ assert (Category.leaves.include? categories(:child_1))
105
+ assert (Category.leaves.include? categories(:child_2_1))
106
+ assert (Category.leaves.include? categories(:child_3))
107
+ assert (Category.leaves.include? categories(:top_level_2))
108
+ end
109
+
110
+ def test_leaf
111
+ assert categories(:child_1).leaf?
112
+ assert categories(:child_2_1).leaf?
113
+ assert categories(:child_3).leaf?
114
+ assert categories(:top_level_2).leaf?
115
+
116
+ assert !categories(:top_level).leaf?
117
+ assert !categories(:child_2).leaf?
118
+ end
119
+
120
+ def test_parent
121
+ assert_equal categories(:child_2), categories(:child_2_1).parent
122
+ end
123
+
124
+ def test_self_and_ancestors
125
+ child = categories(:child_2_1)
126
+ self_and_ancestors = [categories(:top_level), categories(:child_2), child]
127
+ assert_equal self_and_ancestors, child.self_and_ancestors
128
+ end
129
+
130
+ def test_ancestors
131
+ child = categories(:child_2_1)
132
+ ancestors = [categories(:top_level), categories(:child_2)]
133
+ assert_equal ancestors, child.ancestors
134
+ end
135
+
136
+ def test_self_and_siblings
137
+ child = categories(:child_2)
138
+ self_and_siblings = [categories(:child_1), child, categories(:child_3)]
139
+ assert_equal self_and_siblings, child.self_and_siblings
140
+ assert_nothing_raised do
141
+ tops = [categories(:top_level), categories(:top_level_2)]
142
+ assert_equal tops, categories(:top_level).self_and_siblings
143
+ end
144
+ end
145
+
146
+ def test_siblings
147
+ child = categories(:child_2)
148
+ siblings = [categories(:child_1), categories(:child_3)]
149
+ assert_equal siblings, child.siblings
150
+ end
151
+
152
+ def test_leaves
153
+ leaves = [categories(:child_1), categories(:child_2_1), categories(:child_3), categories(:top_level_2)]
154
+ assert categories(:top_level).leaves, leaves
155
+ end
156
+
157
+ def test_level
158
+ assert_equal 0, categories(:top_level).level
159
+ assert_equal 1, categories(:child_1).level
160
+ assert_equal 2, categories(:child_2_1).level
161
+ end
162
+
163
+ def test_has_children?
164
+ assert categories(:child_2_1).children.empty?
165
+ assert !categories(:child_2).children.empty?
166
+ assert !categories(:top_level).children.empty?
167
+ end
168
+
169
+ def test_self_and_descendents
170
+ parent = categories(:top_level)
171
+ self_and_descendants = [parent, categories(:child_1), categories(:child_2),
172
+ categories(:child_2_1), categories(:child_3)]
173
+ assert_equal self_and_descendants, parent.self_and_descendants
174
+ assert_equal self_and_descendants, parent.self_and_descendants.count
175
+ end
176
+
177
+ def test_descendents
178
+ lawyers = Category.create!(:name => "lawyers")
179
+ us = Category.create!(:name => "United States")
180
+ us.move_to_child_of(lawyers)
181
+ patent = Category.create!(:name => "Patent Law")
182
+ patent.move_to_child_of(us)
183
+ lawyers.reload
184
+
185
+ assert_equal 1, lawyers.children.size
186
+ assert_equal 1, us.children.size
187
+ assert_equal 2, lawyers.descendants.size
188
+ end
189
+
190
+ def test_self_and_descendents
191
+ parent = categories(:top_level)
192
+ descendants = [categories(:child_1), categories(:child_2),
193
+ categories(:child_2_1), categories(:child_3)]
194
+ assert_equal descendants, parent.descendants
195
+ end
196
+
197
+ def test_children
198
+ category = categories(:top_level)
199
+ category.children.each {|c| assert_equal category.id, c.parent_id }
200
+ end
201
+
202
+ def test_is_or_is_ancestor_of?
203
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_1))
204
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(:child_2_1))
205
+ assert categories(:child_2).is_or_is_ancestor_of?(categories(:child_2_1))
206
+ assert !categories(:child_2_1).is_or_is_ancestor_of?(categories(:child_2))
207
+ assert !categories(:child_1).is_or_is_ancestor_of?(categories(:child_2))
208
+ assert categories(:child_1).is_or_is_ancestor_of?(categories(:child_1))
209
+ end
210
+
211
+ def test_is_ancestor_of?
212
+ assert categories(:top_level).is_ancestor_of?(categories(:child_1))
213
+ assert categories(:top_level).is_ancestor_of?(categories(:child_2_1))
214
+ assert categories(:child_2).is_ancestor_of?(categories(:child_2_1))
215
+ assert !categories(:child_2_1).is_ancestor_of?(categories(:child_2))
216
+ assert !categories(:child_1).is_ancestor_of?(categories(:child_2))
217
+ assert !categories(:child_1).is_ancestor_of?(categories(:child_1))
218
+ end
219
+
220
+ def test_is_or_is_ancestor_of_with_scope
221
+ root = Scoped.root
222
+ child = root.children.first
223
+ assert root.is_or_is_ancestor_of?(child)
224
+ child.update_attribute :organization_id, 'different'
225
+ assert !root.is_or_is_ancestor_of?(child)
226
+ end
227
+
228
+ def test_is_or_is_descendant_of?
229
+ assert categories(:child_1).is_or_is_descendant_of?(categories(:top_level))
230
+ assert categories(:child_2_1).is_or_is_descendant_of?(categories(:top_level))
231
+ assert categories(:child_2_1).is_or_is_descendant_of?(categories(:child_2))
232
+ assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_2_1))
233
+ assert !categories(:child_2).is_or_is_descendant_of?(categories(:child_1))
234
+ assert categories(:child_1).is_or_is_descendant_of?(categories(:child_1))
235
+ end
236
+
237
+ def test_is_descendant_of?
238
+ assert categories(:child_1).is_descendant_of?(categories(:top_level))
239
+ assert categories(:child_2_1).is_descendant_of?(categories(:top_level))
240
+ assert categories(:child_2_1).is_descendant_of?(categories(:child_2))
241
+ assert !categories(:child_2).is_descendant_of?(categories(:child_2_1))
242
+ assert !categories(:child_2).is_descendant_of?(categories(:child_1))
243
+ assert !categories(:child_1).is_descendant_of?(categories(:child_1))
244
+ end
245
+
246
+ def test_is_or_is_descendant_of_with_scope
247
+ root = Scoped.root
248
+ child = root.children.first
249
+ assert child.is_or_is_descendant_of?(root)
250
+ child.update_attribute :organization_id, 'different'
251
+ assert !child.is_or_is_descendant_of?(root)
252
+ end
253
+
254
+ def test_same_scope?
255
+ root = Scoped.root
256
+ child = root.children.first
257
+ assert child.same_scope?(root)
258
+ child.update_attribute :organization_id, 'different'
259
+ assert !child.same_scope?(root)
260
+ end
261
+
262
+ def test_left_sibling
263
+ assert_equal categories(:child_1), categories(:child_2).left_sibling
264
+ assert_equal categories(:child_2), categories(:child_3).left_sibling
265
+ end
266
+
267
+ def test_left_sibling_of_root
268
+ assert_nil categories(:top_level).left_sibling
269
+ end
270
+
271
+ def test_left_sibling_without_siblings
272
+ assert_nil categories(:child_2_1).left_sibling
273
+ end
274
+
275
+ def test_left_sibling_of_leftmost_node
276
+ assert_nil categories(:child_1).left_sibling
277
+ end
278
+
279
+ def test_right_sibling
280
+ assert_equal categories(:child_3), categories(:child_2).right_sibling
281
+ assert_equal categories(:child_2), categories(:child_1).right_sibling
282
+ end
283
+
284
+ def test_right_sibling_of_root
285
+ assert_equal categories(:top_level_2), categories(:top_level).right_sibling
286
+ assert_nil categories(:top_level_2).right_sibling
287
+ end
288
+
289
+ def test_right_sibling_without_siblings
290
+ assert_nil categories(:child_2_1).right_sibling
291
+ end
292
+
293
+ def test_right_sibling_of_rightmost_node
294
+ assert_nil categories(:child_3).right_sibling
295
+ end
296
+
297
+ def test_move_left
298
+ categories(:child_2).move_left
299
+ assert_nil categories(:child_2).left_sibling
300
+ assert_equal categories(:child_1), categories(:child_2).right_sibling
301
+ assert Category.valid?
302
+ end
303
+
304
+ def test_move_right
305
+ categories(:child_2).move_right
306
+ assert_nil categories(:child_2).right_sibling
307
+ assert_equal categories(:child_3), categories(:child_2).left_sibling
308
+ assert Category.valid?
309
+ end
310
+
311
+ def test_move_to_left_of
312
+ categories(:child_3).move_to_left_of(categories(:child_1))
313
+ assert_nil categories(:child_3).left_sibling
314
+ assert_equal categories(:child_1), categories(:child_3).right_sibling
315
+ assert Category.valid?
316
+ end
317
+
318
+ def test_move_to_right_of
319
+ categories(:child_1).move_to_right_of(categories(:child_3))
320
+ assert_nil categories(:child_1).right_sibling
321
+ assert_equal categories(:child_3), categories(:child_1).left_sibling
322
+ assert Category.valid?
323
+ end
324
+
325
+ def test_move_to_root
326
+ categories(:child_2).move_to_root
327
+ assert_nil categories(:child_2).parent
328
+ assert_equal 0, categories(:child_2).level
329
+ assert_equal 1, categories(:child_2_1).level
330
+ assert_equal 1, categories(:child_2).left
331
+ assert_equal 4, categories(:child_2).right
332
+ assert Category.valid?
333
+ end
334
+
335
+ def test_move_to_child_of
336
+ categories(:child_1).move_to_child_of(categories(:child_3))
337
+ assert_equal categories(:child_3).id, categories(:child_1).parent_id
338
+ assert Category.valid?
339
+ end
340
+
341
+ def test_move_to_child_of_appends_to_end
342
+ child = Category.create! :name => 'New Child'
343
+ child.move_to_child_of categories(:top_level)
344
+ assert_equal child, categories(:top_level).children.last
345
+ end
346
+
347
+ def test_subtree_move_to_child_of
348
+ assert_equal 4, categories(:child_2).left
349
+ assert_equal 7, categories(:child_2).right
350
+
351
+ assert_equal 2, categories(:child_1).left
352
+ assert_equal 3, categories(:child_1).right
353
+
354
+ categories(:child_2).move_to_child_of(categories(:child_1))
355
+ assert Category.valid?
356
+ assert_equal categories(:child_1).id, categories(:child_2).parent_id
357
+
358
+ assert_equal 3, categories(:child_2).left
359
+ assert_equal 6, categories(:child_2).right
360
+ assert_equal 2, categories(:child_1).left
361
+ assert_equal 7, categories(:child_1).right
362
+ end
363
+
364
+ def test_slightly_difficult_move_to_child_of
365
+ assert_equal 11, categories(:top_level_2).left
366
+ assert_equal 12, categories(:top_level_2).right
367
+
368
+ # create a new top-level node and move single-node top-level tree inside it.
369
+ new_top = Category.create(:name => 'New Top')
370
+ assert_equal 13, new_top.left
371
+ assert_equal 14, new_top.right
372
+
373
+ categories(:top_level_2).move_to_child_of(new_top)
374
+
375
+ assert Category.valid?
376
+ assert_equal new_top.id, categories(:top_level_2).parent_id
377
+
378
+ assert_equal 12, categories(:top_level_2).left
379
+ assert_equal 13, categories(:top_level_2).right
380
+ assert_equal 11, new_top.left
381
+ assert_equal 14, new_top.right
382
+ end
383
+
384
+ def test_difficult_move_to_child_of
385
+ assert_equal 1, categories(:top_level).left
386
+ assert_equal 10, categories(:top_level).right
387
+ assert_equal 5, categories(:child_2_1).left
388
+ assert_equal 6, categories(:child_2_1).right
389
+
390
+ # create a new top-level node and move an entire top-level tree inside it.
391
+ new_top = Category.create(:name => 'New Top')
392
+ categories(:top_level).move_to_child_of(new_top)
393
+ categories(:child_2_1).reload
394
+ assert Category.valid?
395
+ assert_equal new_top.id, categories(:top_level).parent_id
396
+
397
+ assert_equal 4, categories(:top_level).left
398
+ assert_equal 13, categories(:top_level).right
399
+ assert_equal 8, categories(:child_2_1).left
400
+ assert_equal 9, categories(:child_2_1).right
401
+ end
402
+
403
+ #rebuild swaps the position of the 2 children when added using move_to_child twice onto same parent
404
+ def test_move_to_child_more_than_once_per_parent_rebuild
405
+ root1 = Category.create(:name => 'Root1')
406
+ root2 = Category.create(:name => 'Root2')
407
+ root3 = Category.create(:name => 'Root3')
408
+
409
+ root2.move_to_child_of root1
410
+ root3.move_to_child_of root1
411
+
412
+ output = Category.roots.last.to_text
413
+ Category.update_all('lft = null, rgt = null')
414
+ Category.rebuild!
415
+
416
+ assert_equal Category.roots.last.to_text, output
417
+ end
418
+
419
+ # doing move_to_child twice onto same parent from the furthest right first
420
+ def test_move_to_child_more_than_once_per_parent_outside_in
421
+ node1 = Category.create(:name => 'Node-1')
422
+ node2 = Category.create(:name => 'Node-2')
423
+ node3 = Category.create(:name => 'Node-3')
424
+
425
+ node2.move_to_child_of node1
426
+ node3.move_to_child_of node1
427
+
428
+ output = Category.roots.last.to_text
429
+ Category.update_all('lft = null, rgt = null')
430
+ Category.rebuild!
431
+
432
+ assert_equal Category.roots.last.to_text, output
433
+ end
434
+
435
+
436
+ def test_valid_with_null_lefts
437
+ assert Category.valid?
438
+ Category.update_all('lft = null')
439
+ assert !Category.valid?
440
+ end
441
+
442
+ def test_valid_with_null_rights
443
+ assert Category.valid?
444
+ Category.update_all('rgt = null')
445
+ assert !Category.valid?
446
+ end
447
+
448
+ def test_valid_with_missing_intermediate_node
449
+ # Even though child_2_1 will still exist, it is a sign of a sloppy delete, not an invalid tree.
450
+ assert Category.valid?
451
+ Category.delete(categories(:child_2).id)
452
+ assert Category.valid?
453
+ end
454
+
455
+ def test_valid_with_overlapping_and_rights
456
+ assert Category.valid?
457
+ categories(:top_level_2)['lft'] = 0
458
+ categories(:top_level_2).save
459
+ assert !Category.valid?
460
+ end
461
+
462
+ def test_rebuild
463
+ assert Category.valid?
464
+ before_text = Category.root.to_text
465
+ Category.update_all('lft = null, rgt = null')
466
+ Category.rebuild!
467
+ assert Category.valid?
468
+ assert_equal before_text, Category.root.to_text
469
+ end
470
+
471
+ def test_move_possible_for_sibling
472
+ assert categories(:child_2).move_possible?(categories(:child_1))
473
+ end
474
+
475
+ def test_move_not_possible_to_self
476
+ assert !categories(:top_level).move_possible?(categories(:top_level))
477
+ end
478
+
479
+ def test_move_not_possible_to_parent
480
+ categories(:top_level).descendants.each do |descendant|
481
+ assert !categories(:top_level).move_possible?(descendant)
482
+ assert descendant.move_possible?(categories(:top_level))
483
+ end
484
+ end
485
+
486
+ def test_is_or_is_ancestor_of?
487
+ [:child_1, :child_2, :child_2_1, :child_3].each do |c|
488
+ assert categories(:top_level).is_or_is_ancestor_of?(categories(c))
489
+ end
490
+ assert !categories(:top_level).is_or_is_ancestor_of?(categories(:top_level_2))
491
+ end
492
+
493
+ def test_left_and_rights_valid_with_blank_left
494
+ assert Category.left_and_rights_valid?
495
+ categories(:child_2)[:lft] = nil
496
+ categories(:child_2).save(false)
497
+ assert !Category.left_and_rights_valid?
498
+ end
499
+
500
+ def test_left_and_rights_valid_with_blank_right
501
+ assert Category.left_and_rights_valid?
502
+ categories(:child_2)[:rgt] = nil
503
+ categories(:child_2).save(false)
504
+ assert !Category.left_and_rights_valid?
505
+ end
506
+
507
+ def test_left_and_rights_valid_with_equal
508
+ assert Category.left_and_rights_valid?
509
+ categories(:top_level_2)[:lft] = categories(:top_level_2)[:rgt]
510
+ categories(:top_level_2).save(false)
511
+ assert !Category.left_and_rights_valid?
512
+ end
513
+
514
+ def test_left_and_rights_valid_with_left_equal_to_parent
515
+ assert Category.left_and_rights_valid?
516
+ categories(:child_2)[:lft] = categories(:top_level)[:lft]
517
+ categories(:child_2).save(false)
518
+ assert !Category.left_and_rights_valid?
519
+ end
520
+
521
+ def test_left_and_rights_valid_with_right_equal_to_parent
522
+ assert Category.left_and_rights_valid?
523
+ categories(:child_2)[:rgt] = categories(:top_level)[:rgt]
524
+ categories(:child_2).save(false)
525
+ assert !Category.left_and_rights_valid?
526
+ end
527
+
528
+ def test_moving_dirty_objects_doesnt_invalidate_tree
529
+ r1 = Category.create
530
+ r2 = Category.create
531
+ r3 = Category.create
532
+ r4 = Category.create
533
+ nodes = [r1, r2, r3, r4]
534
+
535
+ r2.move_to_child_of(r1)
536
+ assert Category.valid?
537
+
538
+ r3.move_to_child_of(r1)
539
+ assert Category.valid?
540
+
541
+ r4.move_to_child_of(r2)
542
+ assert Category.valid?
543
+ end
544
+
545
+ def test_multi_scoped_no_duplicates_for_columns?
546
+ assert_nothing_raised do
547
+ Note.no_duplicates_for_columns?
548
+ end
549
+ end
550
+
551
+ def test_multi_scoped_all_roots_valid?
552
+ assert_nothing_raised do
553
+ Note.all_roots_valid?
554
+ end
555
+ end
556
+
557
+ def test_multi_scoped
558
+ note1 = Note.create!(:body => "A", :notable_id => 2, :notable_type => 'Category')
559
+ note2 = Note.create!(:body => "B", :notable_id => 2, :notable_type => 'Category')
560
+ note3 = Note.create!(:body => "C", :notable_id => 2, :notable_type => 'Default')
561
+
562
+ assert_equal [note1, note2], note1.self_and_siblings
563
+ assert_equal [note3], note3.self_and_siblings
564
+ end
565
+
566
+ def test_multi_scoped_rebuild
567
+ root = Note.create!(:body => "A", :notable_id => 3, :notable_type => 'Category')
568
+ child1 = Note.create!(:body => "B", :notable_id => 3, :notable_type => 'Category')
569
+ child2 = Note.create!(:body => "C", :notable_id => 3, :notable_type => 'Category')
570
+
571
+ child1.move_to_child_of root
572
+ child2.move_to_child_of root
573
+
574
+ Note.update_all('lft = null, rgt = null')
575
+ Note.rebuild!
576
+
577
+ assert_equal Note.roots.find_by_body('A'), root
578
+ assert_equal [child1, child2], Note.roots.find_by_body('A').children
579
+ end
580
+
581
+ def test_same_scope_with_multi_scopes
582
+ assert_nothing_raised do
583
+ notes(:scope1).same_scope?(notes(:child_1))
584
+ end
585
+ assert notes(:scope1).same_scope?(notes(:child_1))
586
+ assert notes(:child_1).same_scope?(notes(:scope1))
587
+ assert !notes(:scope1).same_scope?(notes(:scope2))
588
+ end
589
+
590
+ def test_quoting_of_multi_scope_column_names
591
+ assert_equal ["\"notable_id\"", "\"notable_type\""], Note.quoted_scope_column_names
592
+ end
593
+
594
+ end