acts_as_ordered_tree 0.0.7 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|