acts_as_ordered_tree 0.0.7 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/Appraisals +11 -0
- data/Gemfile +5 -1
- data/README.md +44 -10
- data/Rakefile +4 -0
- data/acts_as_ordered_tree.gemspec +12 -11
- data/lib/acts_as_ordered_tree.rb +99 -34
- data/lib/acts_as_ordered_tree/class_methods.rb +29 -0
- data/lib/acts_as_ordered_tree/fake_scope.rb +28 -0
- data/lib/acts_as_ordered_tree/instance_methods.rb +375 -0
- data/lib/acts_as_ordered_tree/validators.rb +15 -0
- data/lib/acts_as_ordered_tree/version.rb +1 -1
- data/spec/acts_as_ordered_tree_spec.rb +710 -205
- data/spec/db/schema.rb +23 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/support/factories.rb +14 -0
- data/spec/support/matchers.rb +132 -0
- data/spec/support/models.rb +23 -0
- metadata +118 -51
- data/init.rb +0 -2
- data/lib/acts_as_ordered_tree/iterator.rb +0 -36
- data/lib/acts_as_ordered_tree/list.rb +0 -100
- data/lib/acts_as_ordered_tree/tree.rb +0 -156
- data/spec/database.yml +0 -3
- data/spec/iterator_spec.rb +0 -73
- data/spec/test_helper.rb +0 -53
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActsAsOrderedTree
|
2
|
+
module Validators #:nodoc:all:
|
3
|
+
class CyclicReferenceValidator < ActiveModel::Validator
|
4
|
+
def validate(record)
|
5
|
+
record.errors.add(:parent, :invalid) if record.is_or_is_ancestor_of?(record.parent)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class ScopeValidator < ActiveModel::Validator
|
10
|
+
def validate(record)
|
11
|
+
record.errors.add(:parent, :scope) unless record.same_scope?(record.parent)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,314 +1,819 @@
|
|
1
|
-
require File.expand_path('../
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
|
3
3
|
describe ActsAsOrderedTree do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
4
|
+
describe "defaults" do
|
5
|
+
subject { Default }
|
6
|
+
|
7
|
+
its(:parent_column) { should eq :parent_id }
|
8
|
+
its(:position_column) { should eq :position }
|
9
|
+
its(:depth_column) { should eq :depth }
|
10
|
+
its(:children_counter_cache_column) { be_nil }
|
11
|
+
|
12
|
+
context "instance" do
|
13
|
+
subject { Default.new }
|
14
|
+
|
15
|
+
it { should_not allow_mass_assignment_of(:position) }
|
16
|
+
it { should_not allow_mass_assignment_of(:depth) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "default with counter cache" do
|
21
|
+
subject { DefaultWithCounterCache }
|
22
|
+
|
23
|
+
its(:children_counter_cache_column) { should eq :categories_count }
|
12
24
|
end
|
13
25
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
26
|
+
describe "renamed columns" do
|
27
|
+
subject { RenamedColumns }
|
28
|
+
|
29
|
+
its(:parent_column) { should eq :mother_id }
|
30
|
+
its(:position_column) { should eq :red }
|
31
|
+
its(:depth_column) { should eq :pitch }
|
32
|
+
|
33
|
+
context "instance" do
|
34
|
+
subject { RenamedColumns.new }
|
20
35
|
|
21
|
-
|
22
|
-
|
23
|
-
Node.position_column.should eq(:position)
|
24
|
-
Node.parent_column.should eq(:parent_id)
|
36
|
+
it { should_not allow_mass_assignment_of(:red) }
|
37
|
+
it { should_not allow_mass_assignment_of(:pitch) }
|
25
38
|
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "creation_with_altered_column_names" do
|
42
|
+
lambda {
|
43
|
+
RenamedColumns.create!()
|
44
|
+
}.should_not raise_exception
|
45
|
+
end
|
26
46
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
47
|
+
describe ".roots" do
|
48
|
+
# create fixture
|
49
|
+
before { FactoryGirl.create_list(:default, 3) }
|
50
|
+
|
51
|
+
subject { Default.roots }
|
52
|
+
|
53
|
+
its(:entries) { should eq Default.where(:parent_id => nil).order(:position).to_a }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe ".leaves" do
|
57
|
+
# create fixture
|
58
|
+
let(:root) { create :default_with_counter_cache }
|
59
|
+
before { create_list :default_with_counter_cache, 2, :parent => root }
|
60
|
+
|
61
|
+
subject { DefaultWithCounterCache }
|
62
|
+
|
63
|
+
it { should respond_to(:leaves) }
|
64
|
+
its(:leaves) { should have(2).items }
|
65
|
+
end
|
66
|
+
|
67
|
+
describe ".root" do
|
68
|
+
# create fixture
|
69
|
+
let!(:root) { create :default }
|
70
|
+
|
71
|
+
context "given a single root node" do
|
72
|
+
subject { root }
|
73
|
+
|
74
|
+
its(:position) { should eq 1 }
|
75
|
+
end
|
76
|
+
|
77
|
+
context "given multiple root nodes" do
|
78
|
+
before { create_list :default, 3 }
|
79
|
+
|
80
|
+
subject { Default }
|
81
|
+
|
82
|
+
its(:root) { should eq root }
|
31
83
|
end
|
32
84
|
end
|
33
85
|
|
34
|
-
describe "
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
86
|
+
describe "#root?, #child?, #leaf?, #branch? and #root" do
|
87
|
+
shared_examples "tree with predicates" do |factory_name|
|
88
|
+
# create fixture
|
89
|
+
let!(:root) { create factory_name }
|
90
|
+
let!(:child) { create factory_name, :parent => root }
|
91
|
+
let!(:grandchild) { create factory_name, :parent => child }
|
92
|
+
|
93
|
+
before { root.reload }
|
94
|
+
before { child.reload }
|
95
|
+
before { grandchild.reload }
|
96
|
+
|
97
|
+
context "given root node" do
|
98
|
+
subject { root }
|
99
|
+
|
100
|
+
it { should be_root }
|
101
|
+
it { should_not be_child }
|
102
|
+
it { should_not be_leaf }
|
103
|
+
it { should be_branch }
|
104
|
+
its(:root) { should eq root }
|
105
|
+
its(:level) { should eq 0 }
|
106
|
+
end
|
107
|
+
|
108
|
+
context "given a branch node with children" do
|
109
|
+
subject { child }
|
110
|
+
|
111
|
+
it { should_not be_root }
|
112
|
+
it { should be_child }
|
113
|
+
it { should_not be_leaf }
|
114
|
+
it { should be_branch }
|
115
|
+
its(:root) { should eq root }
|
116
|
+
its(:level) { should eq 1 }
|
117
|
+
end
|
118
|
+
|
119
|
+
context "given a leaf node" do
|
120
|
+
subject { grandchild }
|
121
|
+
|
122
|
+
it { should_not be_root }
|
123
|
+
it { should be_child }
|
124
|
+
it { should be_leaf }
|
125
|
+
it { should_not be_branch }
|
126
|
+
its(:root) { should eq root }
|
127
|
+
its(:level) { should eq 2 }
|
128
|
+
end
|
129
|
+
|
130
|
+
context "given a new record" do
|
131
|
+
subject { build factory_name }
|
132
|
+
|
133
|
+
it { should_not be_leaf }
|
134
|
+
it { should be_branch }
|
135
|
+
end
|
39
136
|
end
|
40
137
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
138
|
+
it_behaves_like "tree with predicates", :default
|
139
|
+
it_behaves_like "tree with predicates", :default_with_counter_cache
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "#first?, #last?" do
|
143
|
+
let!(:root) { create :default }
|
144
|
+
let!(:child_1) { create :default, :parent => root }
|
145
|
+
let!(:child_2) { create :default, :parent => root }
|
146
|
+
let!(:child_3) { create :default, :parent => root }
|
147
|
+
|
148
|
+
context "given a node without siblings" do
|
149
|
+
subject { root }
|
150
|
+
|
151
|
+
it { should be_first }
|
152
|
+
it { should be_last }
|
45
153
|
end
|
46
154
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
155
|
+
context "given a node, first in the list" do
|
156
|
+
subject { child_1 }
|
157
|
+
|
158
|
+
it { should be_first }
|
159
|
+
it { should_not be_last }
|
51
160
|
end
|
52
161
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
162
|
+
context "given a node, nor first neither last" do
|
163
|
+
subject { child_2 }
|
164
|
+
|
165
|
+
it { should_not be_first }
|
166
|
+
it { should_not be_last }
|
57
167
|
end
|
58
168
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
169
|
+
context "given a node, last in the list" do
|
170
|
+
subject { child_3 }
|
171
|
+
|
172
|
+
it { should_not be_first }
|
173
|
+
it { should be_last }
|
63
174
|
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "#level" do
|
178
|
+
context "given a persistent root node" do
|
179
|
+
subject { create :default }
|
64
180
|
|
65
|
-
|
66
|
-
root.depth.should eq(0)
|
67
|
-
branch.depth.should eq(1)
|
68
|
-
leaf.depth.should eq(2)
|
181
|
+
its(:level) { should eq 0 }
|
69
182
|
end
|
183
|
+
context "given a new root record" do
|
184
|
+
subject { build :default }
|
70
185
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
root
|
186
|
+
its(:level) { should eq 0 }
|
187
|
+
end
|
188
|
+
context "given a persistent node with parent" do
|
189
|
+
let(:root) { create :default }
|
190
|
+
subject { create :default, :parent => root }
|
191
|
+
its(:level) { should eq 1 }
|
192
|
+
end
|
193
|
+
context "given a new node with parent" do
|
194
|
+
let(:root) { create :default }
|
195
|
+
subject { build :default, :parent => root }
|
196
|
+
its(:level) { should eq 1 }
|
76
197
|
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "#self_and_ancestors" do
|
201
|
+
# create fixture
|
202
|
+
let!(:root) { create :default }
|
203
|
+
let!(:child) { create :default, :parent => root }
|
204
|
+
let!(:grandchild) { create :default, :parent => child }
|
205
|
+
|
206
|
+
context "leaf" do
|
207
|
+
subject { grandchild.self_and_ancestors }
|
208
|
+
|
209
|
+
it { should be_a ActiveRecord::Relation }
|
210
|
+
it { should be_loaded }
|
211
|
+
it { should have(3).items }
|
212
|
+
its(:first) { should eq root }
|
213
|
+
its(:last) { should eq subject }
|
214
|
+
end
|
215
|
+
|
216
|
+
context "child" do
|
217
|
+
subject { child.self_and_ancestors }
|
218
|
+
|
219
|
+
it { should be_a ActiveRecord::Relation }
|
220
|
+
it { should be_loaded }
|
221
|
+
it { should have(2).items }
|
222
|
+
its(:first) { should eq root }
|
223
|
+
its(:last) { should eq subject }
|
224
|
+
end
|
225
|
+
|
226
|
+
context "root" do
|
227
|
+
subject { root.self_and_ancestors }
|
228
|
+
|
229
|
+
it { should be_a ActiveRecord::Relation }
|
230
|
+
it { should be_loaded }
|
231
|
+
it { should have(1).item }
|
232
|
+
its(:first) { should eq root }
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "#ancestors" do
|
237
|
+
# create fixture
|
238
|
+
let!(:root) { create :default }
|
239
|
+
let!(:child) { create :default, :parent => root }
|
240
|
+
let!(:grandchild) { create :default, :parent => child }
|
241
|
+
|
242
|
+
context "leaf" do
|
243
|
+
subject { grandchild.ancestors }
|
244
|
+
|
245
|
+
it { should be_a ActiveRecord::Relation }
|
246
|
+
it { should be_loaded }
|
247
|
+
it { should have(2).items }
|
248
|
+
its(:first) { should eq root }
|
249
|
+
its(:last) { should eq child }
|
250
|
+
end
|
251
|
+
|
252
|
+
context "child" do
|
253
|
+
subject { child.ancestors }
|
254
|
+
|
255
|
+
it { should be_a ActiveRecord::Relation }
|
256
|
+
it { should be_loaded }
|
257
|
+
it { should have(1).item }
|
258
|
+
its(:first) { should eq root }
|
259
|
+
end
|
260
|
+
|
261
|
+
context "root" do
|
262
|
+
subject { root.ancestors }
|
263
|
+
|
264
|
+
it { should be_a ActiveRecord::Relation }
|
265
|
+
it { should be_loaded }
|
266
|
+
it { should be_empty }
|
267
|
+
end
|
268
|
+
end
|
77
269
|
|
78
|
-
|
79
|
-
|
270
|
+
describe "#self_and_descendants" do
|
271
|
+
# create fixture
|
272
|
+
let!(:root) { create :default }
|
273
|
+
let!(:child) { create :default, :parent => root }
|
274
|
+
let!(:grandchild) { create :default, :parent => child }
|
80
275
|
|
81
|
-
|
82
|
-
|
83
|
-
root.descendants.last.should eq(last)
|
276
|
+
context "leaf" do
|
277
|
+
subject { grandchild.self_and_descendants }
|
84
278
|
|
85
|
-
|
86
|
-
|
279
|
+
it { should be_a ActiveRecord::Relation }
|
280
|
+
it { should be_loaded }
|
281
|
+
it { should have(1).item }
|
282
|
+
its(:first) { should eq grandchild }
|
283
|
+
end
|
284
|
+
|
285
|
+
context "child" do
|
286
|
+
subject { child.self_and_descendants }
|
87
287
|
|
88
|
-
|
288
|
+
it { should be_a ActiveRecord::Relation }
|
289
|
+
it { should be_loaded }
|
290
|
+
it { should have(2).items }
|
291
|
+
its(:first) { should eq child }
|
292
|
+
its(:last) { should eq grandchild }
|
89
293
|
end
|
90
294
|
|
91
|
-
|
92
|
-
|
93
|
-
branch.self_and_siblings.should include(branch)
|
295
|
+
context "root" do
|
296
|
+
subject { root.self_and_descendants }
|
94
297
|
|
95
|
-
|
96
|
-
|
298
|
+
it { should be_a ActiveRecord::Relation }
|
299
|
+
it { should be_loaded }
|
300
|
+
it { should have(3).items }
|
301
|
+
its(:first) { should eq root }
|
302
|
+
its(:last) { should eq grandchild }
|
97
303
|
end
|
98
304
|
end
|
99
305
|
|
100
|
-
describe "
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
306
|
+
describe "#is_descendant_of?, #is_or_is_descendant_of?, #is_ancestor_of?, #is_or_is_ancestor_of?" do
|
307
|
+
# create fixture
|
308
|
+
let!(:root) { create :default }
|
309
|
+
let!(:child) { create :default, :parent => root }
|
310
|
+
let!(:grandchild) { create :default, :parent => child }
|
311
|
+
|
312
|
+
context "grandchild" do
|
313
|
+
subject { grandchild }
|
314
|
+
|
315
|
+
it { should be_is_descendant_of(root) }
|
316
|
+
it { should be_is_or_is_descendant_of(root) }
|
317
|
+
it { should_not be_is_ancestor_of(root) }
|
318
|
+
it { should_not be_is_or_is_ancestor_of(root) }
|
319
|
+
|
320
|
+
it { should be_is_descendant_of(child) }
|
321
|
+
it { should be_is_or_is_descendant_of(child) }
|
322
|
+
it { should_not be_is_ancestor_of(child) }
|
323
|
+
it { should_not be_is_or_is_ancestor_of(child) }
|
324
|
+
|
325
|
+
it { should_not be_is_descendant_of(grandchild) }
|
326
|
+
it { should be_is_or_is_descendant_of(grandchild) }
|
327
|
+
it { should_not be_is_ancestor_of(grandchild) }
|
328
|
+
it { should be_is_or_is_ancestor_of(grandchild) }
|
329
|
+
end
|
330
|
+
|
331
|
+
context "child" do
|
332
|
+
subject { child }
|
333
|
+
|
334
|
+
it { should be_is_descendant_of(root) }
|
335
|
+
it { should be_is_or_is_descendant_of(root) }
|
336
|
+
it { should_not be_is_ancestor_of(root) }
|
337
|
+
it { should_not be_is_or_is_ancestor_of(root) }
|
338
|
+
|
339
|
+
it { should_not be_is_descendant_of(child) }
|
340
|
+
it { should be_is_or_is_descendant_of(child) }
|
341
|
+
it { should_not be_is_ancestor_of(child) }
|
342
|
+
it { should be_is_or_is_ancestor_of(child) }
|
343
|
+
|
344
|
+
it { should_not be_is_descendant_of(grandchild) }
|
345
|
+
it { should_not be_is_or_is_descendant_of(grandchild) }
|
346
|
+
it { should be_is_ancestor_of(grandchild) }
|
347
|
+
it { should be_is_or_is_ancestor_of(grandchild) }
|
105
348
|
end
|
106
349
|
|
107
|
-
|
108
|
-
|
350
|
+
context "root" do
|
351
|
+
subject { root }
|
352
|
+
|
353
|
+
it { should_not be_is_descendant_of(root) }
|
354
|
+
it { should be_is_or_is_descendant_of(root) }
|
355
|
+
it { should_not be_is_ancestor_of(root) }
|
356
|
+
it { should be_is_or_is_ancestor_of(root) }
|
357
|
+
|
358
|
+
it { should_not be_is_descendant_of(child) }
|
359
|
+
it { should_not be_is_or_is_descendant_of(child) }
|
360
|
+
it { should be_is_ancestor_of(child) }
|
361
|
+
it { should be_is_or_is_ancestor_of(child) }
|
362
|
+
|
363
|
+
it { should_not be_is_descendant_of(grandchild) }
|
364
|
+
it { should_not be_is_or_is_descendant_of(grandchild) }
|
365
|
+
it { should be_is_ancestor_of(grandchild) }
|
366
|
+
it { should be_is_or_is_ancestor_of(grandchild) }
|
109
367
|
end
|
110
368
|
end
|
111
369
|
|
112
|
-
describe "
|
113
|
-
|
114
|
-
|
115
|
-
|
370
|
+
describe "#left_sibling" do
|
371
|
+
shared_examples "tree with siblings" do
|
372
|
+
subject { items }
|
373
|
+
|
374
|
+
its('first.left_sibling') { should be_nil }
|
375
|
+
its('first.right_sibling') { should eq items.second }
|
376
|
+
|
377
|
+
its('second.left_sibling') { should eq items.first }
|
378
|
+
its('second.right_sibling') { should eq items.last }
|
116
379
|
|
117
|
-
|
380
|
+
its('third.left_sibling') { should eq items.second }
|
381
|
+
its('third.right_sibling') { should be_nil }
|
382
|
+
end
|
383
|
+
|
384
|
+
context "given unscoped tree" do
|
385
|
+
it_should_behave_like "tree with siblings" do
|
386
|
+
let(:items) { create_list :default, 3 }
|
118
387
|
end
|
119
388
|
end
|
120
389
|
|
121
|
-
|
122
|
-
|
123
|
-
|
390
|
+
context "given scoped tree" do
|
391
|
+
let!(:items_1) { create_list :scoped, 3, :scope_type => "s1" }
|
392
|
+
let!(:items_2) { create_list :scoped, 3, :scope_type => "s2" }
|
393
|
+
|
394
|
+
it_should_behave_like "tree with siblings" do
|
395
|
+
let(:items) { items_1 }
|
396
|
+
end
|
397
|
+
it_should_behave_like "tree with siblings" do
|
398
|
+
let(:items) { items_2 }
|
399
|
+
end
|
124
400
|
end
|
401
|
+
end
|
125
402
|
|
126
|
-
|
127
|
-
|
128
|
-
blank.save
|
403
|
+
describe "#reload_node" do
|
404
|
+
let!(:node) { create :default }
|
129
405
|
|
130
|
-
|
131
|
-
|
132
|
-
|
406
|
+
before do
|
407
|
+
node.name = 'changed'
|
408
|
+
node.parent_id = 200
|
409
|
+
node.position = 1000
|
133
410
|
end
|
134
411
|
|
135
|
-
|
136
|
-
last_child = branch.children.last
|
412
|
+
subject { node.send :reload_node }
|
137
413
|
|
138
|
-
|
139
|
-
|
414
|
+
its(:name) { should eq 'changed' }
|
415
|
+
its(:parent_id) { should be_nil }
|
416
|
+
its(:position) { should eq 1 }
|
417
|
+
end
|
418
|
+
|
419
|
+
describe "move actions" do
|
420
|
+
let!(:root) { create :default_with_counter_cache, :name => 'root' }
|
421
|
+
let!(:child_1) { create :default_with_counter_cache, :parent => root, :name => 'child_1' }
|
422
|
+
let!(:child_2) { create :default_with_counter_cache, :parent => root, :name => 'child_2' }
|
423
|
+
let!(:child_3) { create :default_with_counter_cache, :parent => root, :name => 'child_3' }
|
140
424
|
|
141
|
-
|
142
|
-
|
425
|
+
context "initial" do
|
426
|
+
specify { expect([child_1, child_2, child_3]).to be_sorted }
|
143
427
|
|
144
|
-
|
145
|
-
|
428
|
+
subject { root.reload }
|
429
|
+
its(:parent_id) { should be_nil }
|
430
|
+
its(:level) { should be_zero }
|
431
|
+
its(:position) { should eq 1 }
|
432
|
+
its(:categories_count) { should eq 3}
|
146
433
|
end
|
147
434
|
|
148
|
-
|
149
|
-
|
435
|
+
context "on_save_when_parent_changed" do
|
436
|
+
example "move_1_to_root" do
|
437
|
+
child_1.parent = nil
|
438
|
+
child_1.save
|
439
|
+
expect(child_1.position).to eq 2
|
440
|
+
expect([root, child_1]).to be_sorted
|
441
|
+
end
|
150
442
|
|
151
|
-
|
152
|
-
|
153
|
-
|
443
|
+
example "move_3_to_root" do
|
444
|
+
child_3.parent = nil
|
445
|
+
child_3.save
|
446
|
+
expect(child_3.position).to eq 2
|
447
|
+
expect([root, child_3]).to be_sorted
|
448
|
+
end
|
154
449
|
end
|
155
450
|
|
156
|
-
|
157
|
-
|
158
|
-
|
451
|
+
describe "#move_left" do
|
452
|
+
example "move_1_left" do
|
453
|
+
expect{ child_1.move_left }.to raise_exception ActiveRecord::ActiveRecordError
|
454
|
+
expect([child_1, child_2, child_3]).to be_sorted
|
455
|
+
end
|
159
456
|
|
160
|
-
|
457
|
+
example "move_2_left" do
|
458
|
+
child_2.move_left
|
459
|
+
expect([child_2, child_1, child_3]).to be_sorted
|
460
|
+
end
|
461
|
+
|
462
|
+
example "move_3_left" do
|
463
|
+
child_3.move_left
|
464
|
+
expect([child_1, child_3, child_2]).to be_sorted
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
describe "#move_right" do
|
469
|
+
example "move_3_right" do
|
470
|
+
expect{ child_3.move_right }.to raise_exception ActiveRecord::ActiveRecordError
|
471
|
+
expect([child_1, child_2, child_3]).to be_sorted
|
472
|
+
end
|
161
473
|
|
162
|
-
|
163
|
-
|
474
|
+
example "move_2_right" do
|
475
|
+
child_2.move_right
|
476
|
+
expect([child_1, child_3, child_2]).to be_sorted
|
477
|
+
end
|
164
478
|
|
165
|
-
|
479
|
+
example "move_1_right" do
|
480
|
+
child_1.move_right
|
481
|
+
expect([child_2, child_1, child_3]).to be_sorted
|
482
|
+
end
|
166
483
|
end
|
167
484
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
485
|
+
describe "#move_to_left_of" do
|
486
|
+
example "move_3_to_left_of_1" do
|
487
|
+
child_3.move_to_left_of child_1
|
488
|
+
expect([child_3, child_1, child_2]).to be_sorted
|
489
|
+
end
|
490
|
+
|
491
|
+
example "move_3_to_left_of_2" do
|
492
|
+
child_3.move_to_left_of child_2
|
493
|
+
expect([child_1, child_3, child_2]).to be_sorted
|
494
|
+
end
|
175
495
|
|
176
|
-
|
496
|
+
example "move_1_to_left_of_3" do
|
497
|
+
child_1.move_to_left_of child_3
|
498
|
+
expect([child_2, child_1, child_3]).to be_sorted
|
499
|
+
end
|
177
500
|
|
178
|
-
|
179
|
-
|
501
|
+
example "move_1_to_left_of_3_id" do
|
502
|
+
child_1.move_to_left_of child_3.id
|
503
|
+
expect([child_2, child_1, child_3]).to be_sorted
|
504
|
+
end
|
505
|
+
|
506
|
+
example "move_root_to_left_of_child_2" do
|
507
|
+
expect{ root.move_to_left_of child_2 }.to raise_exception ActiveRecord::ActiveRecordError
|
508
|
+
end
|
180
509
|
end
|
181
510
|
|
182
|
-
|
183
|
-
|
511
|
+
describe "#move_to_right_of" do
|
512
|
+
example "move_1_to_right_of_2" do
|
513
|
+
child_1.move_to_right_of child_2
|
514
|
+
expect([child_2, child_1, child_3]).to be_sorted
|
515
|
+
end
|
184
516
|
|
185
|
-
|
186
|
-
|
517
|
+
example "move_1_to_right_of_3" do
|
518
|
+
child_1.move_to_right_of child_3
|
519
|
+
expect([child_2, child_3, child_1]).to be_sorted
|
520
|
+
end
|
187
521
|
|
188
|
-
|
189
|
-
|
522
|
+
example "move_1_to_right_of_3_id" do
|
523
|
+
child_1.move_to_right_of child_3.id
|
524
|
+
expect([child_2, child_3, child_1]).to be_sorted
|
525
|
+
end
|
526
|
+
|
527
|
+
example "move_3_to_right_of_1" do
|
528
|
+
child_3.move_to_right_of child_1
|
529
|
+
expect([child_1, child_3, child_2]).to be_sorted
|
530
|
+
end
|
531
|
+
|
532
|
+
example "move_root_to_right_of_child_2" do
|
533
|
+
expect{ root.move_to_right_of child_2 }.to raise_exception ActiveRecord::ActiveRecordError
|
534
|
+
end
|
190
535
|
end
|
191
536
|
|
192
|
-
|
193
|
-
|
537
|
+
describe "#move_to_root" do
|
538
|
+
before { child_2.move_to_root }
|
539
|
+
|
540
|
+
context "child_2" do
|
541
|
+
subject { child_2 }
|
194
542
|
|
195
|
-
|
196
|
-
|
197
|
-
|
543
|
+
its(:level) { should be_zero }
|
544
|
+
its(:parent_id) { should be_nil }
|
545
|
+
its(:position) { should eq 2 }
|
546
|
+
|
547
|
+
it "should not become new root" do
|
548
|
+
DefaultWithCounterCache.root.should eq root
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
context "other_nodes" do
|
553
|
+
specify { child_1.reload.position.should eq 1 }
|
554
|
+
specify { child_3.reload.position.should eq 2 }
|
555
|
+
specify { root.reload.categories_count.should eq 2 }
|
556
|
+
end
|
557
|
+
|
558
|
+
|
559
|
+
context "given a root node" do
|
560
|
+
before { root.move_to_root }
|
561
|
+
subject { root }
|
562
|
+
|
563
|
+
its(:position) { should eq 1 }
|
564
|
+
|
565
|
+
it "positions should not change" do
|
566
|
+
expect([root, child_3]).to be_sorted
|
567
|
+
end
|
568
|
+
end
|
198
569
|
end
|
199
570
|
|
200
|
-
|
201
|
-
|
202
|
-
above_of = second_branch.children.first
|
571
|
+
describe "#move_to_child_of" do
|
572
|
+
let(:moved_child) { create :default_with_counter_cache, :name => 'moved_child' }
|
203
573
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
574
|
+
before { moved_child.move_to_child_of root }
|
575
|
+
context "moved_child" do
|
576
|
+
subject { moved_child }
|
577
|
+
its(:level) { should eq 1 }
|
578
|
+
its(:position) { should eq 4 }
|
579
|
+
end
|
580
|
+
|
581
|
+
context "root" do
|
582
|
+
subject { root.reload }
|
583
|
+
its(:right_sibling) { should be_nil }
|
584
|
+
its(:categories_count) { should eq 4 }
|
585
|
+
end
|
586
|
+
|
587
|
+
context "given a node which already is children of target" do
|
588
|
+
subject { child_2 }
|
589
|
+
before { child_2.move_to_child_of root }
|
590
|
+
|
591
|
+
its(:position) { should eq 2 }
|
592
|
+
|
593
|
+
it "positions_should_not_change" do
|
594
|
+
expect([child_1, child_2, child_3, moved_child]).to be_sorted
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
it { expect([child_1, child_2, child_3, moved_child]).to be_sorted }
|
599
|
+
it { expect{ root.move_to_child_of root }.to raise_exception ActiveRecord::ActiveRecordError }
|
600
|
+
it { expect{ root.move_to_child_of child_1 }.to raise_exception ActiveRecord::ActiveRecordError }
|
209
601
|
end
|
210
602
|
|
211
|
-
|
212
|
-
|
603
|
+
describe "#move_to_child_with_index" do
|
604
|
+
let(:moved_child) { create :default, :name => 'moved_child' }
|
605
|
+
|
606
|
+
example "move_to_child_as_first" do
|
607
|
+
moved_child.move_to_child_with_index root, 0
|
608
|
+
expect([moved_child, child_1, child_2, child_3]).to be_sorted
|
609
|
+
moved_child.position.should eq 1
|
610
|
+
end
|
611
|
+
|
612
|
+
example "move_to_child_as_second" do
|
613
|
+
moved_child.move_to_child_with_index root, 1
|
614
|
+
expect([child_1, moved_child, child_2, child_3]).to be_sorted
|
615
|
+
moved_child.position.should eq 2
|
616
|
+
end
|
617
|
+
|
618
|
+
example "move_to_child_as_third" do
|
619
|
+
moved_child.move_to_child_with_index root, 2
|
620
|
+
expect([child_1, child_2, moved_child, child_3]).to be_sorted
|
621
|
+
moved_child.position.should eq 3
|
622
|
+
end
|
623
|
+
|
624
|
+
example "move_to_child_as_last" do
|
625
|
+
moved_child.move_to_child_with_index root, 3
|
626
|
+
expect([child_1, child_2, child_3, moved_child]).to be_sorted
|
627
|
+
moved_child.position.should eq 4
|
628
|
+
end
|
629
|
+
|
630
|
+
example "move_child_to_root_as_first" do
|
631
|
+
child_3.move_to_child_with_index nil, 0
|
632
|
+
child_3.level.should be_zero
|
633
|
+
expect([child_3, root, moved_child]).to be_sorted
|
634
|
+
expect([child_1, child_2]).to be_sorted
|
635
|
+
child_2.right_sibling.should be_nil
|
636
|
+
end
|
637
|
+
|
638
|
+
example "move_to_child_with_large_index" do
|
639
|
+
moved_child.move_to_child_with_index root, 100
|
640
|
+
expect([child_1, child_2, child_3, moved_child]).to be_sorted
|
641
|
+
moved_child.position.should eq 4
|
642
|
+
end
|
643
|
+
|
644
|
+
example "move_to_child_with_negative_index" do
|
645
|
+
moved_child.move_to_child_with_index root, -2
|
646
|
+
expect([child_1, child_2, moved_child, child_3]).to be_sorted
|
647
|
+
moved_child.position.should eq 3
|
648
|
+
end
|
649
|
+
|
650
|
+
example "move_to_child_with_large_negative_index" do
|
651
|
+
expect{ moved_child.move_to_child_with_index root, -100 }.to raise_exception ActiveRecord::ActiveRecordError
|
652
|
+
end
|
213
653
|
|
214
|
-
|
654
|
+
example "move_to_child_with_nil_index" do
|
655
|
+
expect{ moved_child.move_to_child_with_index root, nil }.to raise_exception ActiveRecord::ActiveRecordError
|
656
|
+
end
|
215
657
|
|
216
|
-
|
217
|
-
|
658
|
+
example "move_to_child_with_float_index" do
|
659
|
+
moved_child.move_to_child_with_index root, 1.7
|
660
|
+
expect([child_1, moved_child, child_2, child_3]).to be_sorted
|
661
|
+
end
|
662
|
+
|
663
|
+
example "move_root_to_child_of_self" do
|
664
|
+
expect{ root.move_to_child_with_index child_1, 1 }.to raise_exception ActiveRecord::ActiveRecordError
|
665
|
+
end
|
218
666
|
|
219
|
-
first_child.position.should eq(2)
|
220
|
-
second.reload.position.should eq(3)
|
221
667
|
end
|
222
668
|
|
223
|
-
|
224
|
-
|
669
|
+
describe "#insert_at" do
|
670
|
+
before { child_3.insert_at(1) }
|
671
|
+
before { child_3.reload }
|
225
672
|
|
226
|
-
|
227
|
-
branch.children.first.position.should eq(1)
|
673
|
+
specify { expect([child_3, child_1, child_2]).to be_sorted }
|
228
674
|
end
|
229
675
|
|
230
676
|
describe "callbacks" do
|
231
|
-
|
232
|
-
|
677
|
+
subject { child_3 }
|
678
|
+
|
679
|
+
it { should fire_callback(:before_move).when_calling(:move_to_root).once }
|
680
|
+
it { should fire_callback(:after_move).when_calling(:move_to_root).once }
|
681
|
+
it { should fire_callback(:around_move).when_calling(:move_to_root).once }
|
682
|
+
|
683
|
+
it { should_not fire_callback(:before_move).when_calling(:move_left) }
|
684
|
+
it { should_not fire_callback(:after_move).when_calling(:move_left) }
|
685
|
+
it { should_not fire_callback(:around_move).when_calling(:move_left) }
|
686
|
+
|
687
|
+
it { should fire_callback(:before_reorder).when_calling(:move_higher).once }
|
688
|
+
it { should fire_callback(:after_reorder).when_calling(:move_higher).once }
|
689
|
+
|
690
|
+
it { should_not fire_callback(:before_reorder).when_calling(:move_to_root) }
|
233
691
|
|
234
|
-
|
235
|
-
|
236
|
-
second_branch.should_receive(:on_after_reorder).exactly(examples_count)
|
692
|
+
it "should cache depth on save" do
|
693
|
+
record = build :default_with_counter_cache
|
237
694
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
695
|
+
record.depth.should be_nil
|
696
|
+
record.save
|
697
|
+
|
698
|
+
record.depth.should eq 0
|
699
|
+
|
700
|
+
record.move_to_left_of child_3
|
701
|
+
record.depth.should eq child_3.level
|
244
702
|
end
|
245
703
|
|
246
|
-
it "should
|
247
|
-
|
248
|
-
|
249
|
-
leaf.should_not_receive(:on_after_reorder)
|
704
|
+
it "should recalculate depth of descendants" do
|
705
|
+
record = create :default_with_counter_cache, :parent => child_3
|
706
|
+
record.depth.should eq 2
|
250
707
|
|
251
|
-
|
252
|
-
|
708
|
+
child_3.move_to_root
|
709
|
+
record.reload.depth.should eq 1
|
253
710
|
|
254
|
-
|
255
|
-
|
256
|
-
leaf.move_to_child_of(p2)
|
257
|
-
leaf.move_to_bottom_of(p1.children.first)
|
711
|
+
child_3.move_to_child_of child_1
|
712
|
+
record.reload.depth.should eq 3
|
258
713
|
end
|
714
|
+
end
|
715
|
+
|
716
|
+
end
|
259
717
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
718
|
+
describe "scoped trees" do
|
719
|
+
let!(:root1) { create :scoped, :scope_type => "t1" }
|
720
|
+
let!(:child1) { create :scoped, :parent => root1 }
|
721
|
+
let!(:orphan) do
|
722
|
+
record = create :scoped, :parent => root1
|
723
|
+
record.class.update_all({:scope_type => "t0", :position => 1}, {:id => record.id})
|
724
|
+
record
|
725
|
+
end
|
264
726
|
|
265
|
-
|
266
|
-
|
267
|
-
last.should_not_receive(:on_after_reorder)
|
727
|
+
let!(:root2) { create :scoped, :scope_type => "t2" }
|
728
|
+
let!(:child2) { create :scoped, :scope_type => "t2", :parent => root2 }
|
268
729
|
|
269
|
-
|
270
|
-
|
730
|
+
it "should not stick positions together for different scopes" do
|
731
|
+
root1.position.should eq root2.position
|
732
|
+
end
|
733
|
+
it "should automatically set scope for new records with parent" do
|
734
|
+
child1.should be_same_scope(root1)
|
735
|
+
end
|
736
|
+
it "should not include orphans" do
|
737
|
+
root1.children.reload.should_not include orphan
|
738
|
+
root1.descendants.reload.should_not include orphan
|
739
|
+
end
|
740
|
+
it "should not allow to move records between scopes" do
|
741
|
+
expect { child2.move_to_child_of root1 }.to raise_error(ActiveRecord::ActiveRecordError)
|
742
|
+
end
|
743
|
+
it "should not allow to change scope" do
|
744
|
+
child2.parent = root1
|
745
|
+
child2.should have_at_least(1).error_on(:parent)
|
746
|
+
end
|
747
|
+
it "should not allow to add scoped record to children collection" do
|
748
|
+
root1.children << child2
|
749
|
+
root1.children.reload.should_not include child2
|
750
|
+
end
|
751
|
+
end
|
271
752
|
|
272
|
-
|
273
|
-
|
274
|
-
|
753
|
+
describe "#destroy behavior" do
|
754
|
+
let!(:root) { create :default_with_counter_cache, :name => 'root' }
|
755
|
+
let!(:child_1) { create :default_with_counter_cache, :parent => root, :name => 'child_1' }
|
756
|
+
let!(:child_2) { create :default_with_counter_cache, :parent => root, :name => 'child_2' }
|
757
|
+
let!(:child_3) { create :default_with_counter_cache, :parent => root, :name => 'child_3' }
|
275
758
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
leaf.should_receive(:on_after_move).exactly(examples_count)
|
280
|
-
leaf.should_receive(:on_around_move).exactly(examples_count)
|
759
|
+
describe "it should destroy descendants" do
|
760
|
+
subject { root }
|
761
|
+
before { subject.destroy }
|
281
762
|
|
282
|
-
|
283
|
-
|
763
|
+
it { should be_destroyed }
|
764
|
+
its('descendants.reload') { should be_empty }
|
284
765
|
|
285
|
-
|
286
|
-
|
287
|
-
leaf.move_to_bottom_of(p1.children.first)
|
766
|
+
specify "ensure the loneliness" do
|
767
|
+
root.class.all.should be_empty
|
288
768
|
end
|
769
|
+
end
|
770
|
+
|
771
|
+
describe "it should stick positions together" do
|
772
|
+
before { child_2.destroy }
|
773
|
+
before { child_3.reload }
|
774
|
+
|
775
|
+
subject { child_3 }
|
289
776
|
|
290
|
-
|
291
|
-
|
292
|
-
leaf.should_not_receive(:on_after_move)
|
293
|
-
leaf.should_not_receive(:on_around_move)
|
777
|
+
its(:left_sibling) { should eq child_1 }
|
778
|
+
its(:position) { should eq 2 }
|
294
779
|
|
295
|
-
|
296
|
-
|
297
|
-
leaf.move_to_bottom_of(leaf.siblings.first)
|
298
|
-
leaf.reload.save
|
780
|
+
specify "root categories_count should decrease" do
|
781
|
+
root.reload.categories_count.should eq 2
|
299
782
|
end
|
300
783
|
end
|
301
784
|
end
|
302
785
|
|
303
|
-
describe "
|
304
|
-
|
305
|
-
|
306
|
-
|
786
|
+
describe "potential vulnerabilities" do
|
787
|
+
describe "attempt to link parent to one of descendants" do
|
788
|
+
let(:root) { create :default }
|
789
|
+
let(:child) { create :default, :parent => root }
|
790
|
+
let(:grandchild) { create :default, :parent => child }
|
791
|
+
|
792
|
+
subject { root }
|
793
|
+
|
794
|
+
context "given self as parent" do
|
795
|
+
before { root.parent = root }
|
796
|
+
|
797
|
+
it { should have_at_least(1).error_on(:parent) }
|
798
|
+
end
|
799
|
+
|
800
|
+
context "given child as parent" do
|
801
|
+
before { root.parent = child }
|
802
|
+
|
803
|
+
it { should have_at_least(1).error_on(:parent) }
|
804
|
+
end
|
805
|
+
|
806
|
+
context "given grandchild as parent" do
|
807
|
+
before { root.parent = grandchild }
|
808
|
+
|
809
|
+
it { should have_at_least(1).error_on(:parent) }
|
810
|
+
end
|
307
811
|
end
|
308
812
|
|
309
|
-
|
310
|
-
|
311
|
-
|
813
|
+
describe "attempt to create node with wrong position" do
|
814
|
+
it "should not throw error" do
|
815
|
+
expect{ create :default, :position => 22 }.not_to raise_error
|
816
|
+
end
|
312
817
|
end
|
313
818
|
end
|
314
819
|
end
|