mongoid_acts_as_list 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::ActsAsList::List do
4
+ [:position, :number].each do |default_field_name|
5
+ let(:position_field) { default_field_name }
6
+
7
+ before do
8
+ Mongoid::ActsAsList.configure do |config|
9
+ config.default_position_field = position_field
10
+ end
11
+
12
+ require 'fixtures/relational_models'
13
+ end
14
+
15
+
16
+ describe Mongoid::ActsAsList::List::Root do
17
+ let(:category_1) { Category.create! }
18
+ let(:category_2) { Category.create! }
19
+ let(:category_3) { Category.create! }
20
+
21
+ before do
22
+ [category_1, category_2].each do |cat|
23
+ 3.times do |n|
24
+ cat.items.create! position_field => n
25
+ end
26
+ cat.should have(3).items
27
+ end
28
+ end
29
+
30
+ it_behaves_like 'a list'
31
+
32
+ describe ".acts_as_list" do
33
+ it "defines #scope_condition" do
34
+ item = category_1.items.first
35
+ item.scope_condition.should == {:category_id => category_1.id}
36
+ end
37
+
38
+ it "raises a NoScope error if called without a scope option" do
39
+ lambda do
40
+ RootItem.acts_as_list(scope: nil)
41
+ end.should raise_exception Mongoid::ActsAsList::List::ScopeMissingError
42
+ end
43
+ end
44
+
45
+ describe ".order_by_position" do
46
+ it "works with a condition" do
47
+ RootItem.order_by_position(:category_id => category_2.id).map(&position_field).should == [0,1,2]
48
+ end
49
+ end
50
+
51
+ describe "Insert a new item to the list" do
52
+ it "scopes list to the relation" do
53
+ item_1 = category_1.items.create!
54
+ item_2 = category_2.items.create!
55
+
56
+ item_1[position_field].should == item_2[position_field]
57
+ item_2[position_field].should == 3
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ class Category
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ embeds_many :items, class_name: 'EmbeddedItem'
6
+ end
7
+
8
+ class EmbeddedItem
9
+ include Mongoid::Document
10
+ include Mongoid::Timestamps
11
+ include Mongoid::ActsAsList
12
+
13
+ embedded_in :category
14
+ acts_as_list
15
+ end
@@ -0,0 +1,13 @@
1
+ class Category
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+ has_many :items, class_name: 'RootItem'
5
+ end
6
+
7
+ class RootItem
8
+ include Mongoid::Document
9
+ include Mongoid::Timestamps
10
+ include Mongoid::ActsAsList
11
+ belongs_to :category
12
+ acts_as_list :scope => :category
13
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::ActsAsList do
4
+ it "has a version" do
5
+ Mongoid::ActsAsList::VERSION.should match(/^\d+\.\d+\.\d+$/)
6
+ end
7
+ end
@@ -0,0 +1,34 @@
1
+ require 'rspec'
2
+ require 'rspec/autorun'
3
+ require 'database_cleaner'
4
+ require 'database_cleaner/mongoid/truncation'
5
+ require 'pry'
6
+
7
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each do |f|
8
+ require f
9
+ end
10
+
11
+ require 'mongoid'
12
+ require_relative '../lib/mongoid_acts_as_list'
13
+
14
+ Mongoid.configure do |config|
15
+ config.master = Mongo::Connection.new.db('acts_as_list_test')
16
+ end
17
+
18
+ RSpec.configure do |config|
19
+ config.filter_run focus: true
20
+ config.run_all_when_everything_filtered = true
21
+ config.treat_symbols_as_metadata_keys_with_true_values = true
22
+
23
+ config.before(:suite) do
24
+ DatabaseCleaner['mongoid'].strategy = :truncation
25
+ end
26
+
27
+ config.before(:each) do
28
+ DatabaseCleaner.start
29
+ end
30
+
31
+ config.after(:each) do
32
+ DatabaseCleaner.clean
33
+ end
34
+ end
@@ -0,0 +1,523 @@
1
+ shared_examples_for "a list" do
2
+ describe ".acts_as_list" do
3
+ it "defines #position_field && .position_field" do
4
+ item = category_1.items.first
5
+ item.position_field.should == position_field
6
+ item.class.position_field.should == position_field
7
+ end
8
+ end
9
+
10
+ describe ".order_by_position" do
11
+ it "works without conditions" do
12
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
13
+ end
14
+
15
+ it "sorts by created_at if positions are equal" do
16
+ deuce = category_1.items.create! position_field => 1
17
+ items = category_1.items.order_by_position
18
+ items.map(&position_field).should == [0,1,1,2]
19
+ items[2].should == deuce
20
+ end
21
+
22
+ it "sorts in descending order if specified" do
23
+ deuce = category_1.items.create! position_field => 2, :created_at => Date.yesterday
24
+ items = category_1.items.order_by_position(:desc)
25
+ items.map(&position_field).should == [2,2,1,0]
26
+ items[1].should == deuce
27
+ end
28
+ end
29
+
30
+ describe "Insert a new item to the list" do
31
+ it "inserts at the next available position for a given category" do
32
+ item = category_1.items.create!
33
+ item[position_field].should == 3
34
+ end
35
+ end
36
+
37
+ describe "Removing items" do
38
+ before do
39
+ 3.times do
40
+ category_1.items.create!
41
+ end
42
+ category_1.reload.items.map(&position_field).should == [0,1,2,3,4,5]
43
+ end
44
+
45
+ describe " #destroy" do
46
+ it "reorders the positions in the list" do
47
+ item = category_1.items.where(position_field => 3).first
48
+ item.destroy
49
+
50
+ items = item.embedded? ? category_1.items : category_1.reload.items
51
+ items.map(&position_field).should == [0,1,2,3,4]
52
+ end
53
+
54
+
55
+ it "does not shift positions if the element was already removed from the list" do
56
+ item = category_1.items.where(position_field => 2).first
57
+ item.remove_from_list
58
+ item.destroy
59
+ category_1.reload.items.map(&position_field).should == [0,1,2,3,4]
60
+ end
61
+ end
62
+
63
+ describe " #remove_from_list" do
64
+ it "sets position to nil" do
65
+ item = category_1.items.where(position_field => 2).first
66
+ item.remove_from_list
67
+ item[position_field].should be_nil
68
+ end
69
+
70
+ it "is not in list anymore" do
71
+ item = category_1.items.where(position_field => 3).first
72
+ item.remove_from_list
73
+ item.should_not be_in_list
74
+ end
75
+
76
+ it "reorders the positions in the list" do
77
+ category_1.items.where(position_field => 0).first.remove_from_list
78
+ category_1.reload.items.map(&position_field).compact.should == [0,1,2,3,4]
79
+ end
80
+ end
81
+ end
82
+
83
+ describe "#first?" do
84
+ it "returns true if item is the first of the list" do
85
+ category_1.items.order_by_position.first.should be_first
86
+ end
87
+
88
+ it "returns false if item is not the first of the list" do
89
+ all_but_first = category_1.items.order_by_position.to_a[1..-1]
90
+ all_but_first.map(&:first?).uniq.should == [false]
91
+ end
92
+ end
93
+
94
+ describe "#last?" do
95
+ it "returns true if item is the last of the list" do
96
+ category_1.items.order_by_position.last.should be_last
97
+ end
98
+
99
+ it "returns false if item is not the last of the list" do
100
+ all_but_last = category_1.items.order_by_position.to_a[0..-2]
101
+ all_but_last.map(&:last?).uniq.should == [false]
102
+ end
103
+ end
104
+
105
+ %w[higher_item next_item].each do |method_name|
106
+ describe "##{method_name}" do
107
+ it "returns the next item in the list if there is one" do
108
+ item = category_1.items.where(position_field => 1).first
109
+ next_item = category_1.items.where(position_field => 2).first
110
+ item.send(method_name).should == next_item
111
+ end
112
+
113
+ it "returns nil if the item is already the last" do
114
+ item = category_1.items.order_by_position.last
115
+ item.send(method_name).should be_nil
116
+ end
117
+
118
+ it "returns nil if the item is not in the list" do
119
+ item = category_1.items.order_by_position.first
120
+ item.remove_from_list
121
+ item.send(method_name).should be_nil
122
+ end
123
+ end
124
+ end
125
+
126
+ %w[lower_item previous_item].each do |method_name|
127
+ describe "##{method_name}" do
128
+ it "returns the previous item in the list if there is one" do
129
+ item = category_1.items.where(position_field => 1).first
130
+ previous_item = category_1.items.where(position_field => 0).first
131
+ item.send(method_name).should == previous_item
132
+ end
133
+
134
+ it "returns nil if the item is already the first" do
135
+ item = category_1.items.order_by_position.first
136
+ item.send(method_name).should be_nil
137
+ end
138
+
139
+ it "returns nil if the item is not in the list" do
140
+ item = category_1.items.order_by_position.last
141
+ item.remove_from_list
142
+ item.send(method_name).should be_nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "#insert_at" do
148
+ context "to a lower position" do
149
+ let(:item) { category_1.items.order_by_position.last }
150
+
151
+ it "changes the item's position" do
152
+ item.insert_at 1
153
+ item[position_field].should == 1
154
+ end
155
+
156
+ it "shuffles intermediary positions" do
157
+ positions = category_1.items.order_by_position.map(&position_field)
158
+ positions.should == [0,1,2]
159
+ item.insert_at 1
160
+ positions = category_1.items.order_by_position.map(&position_field)
161
+ positions.should == [0,1,2]
162
+ end
163
+
164
+ it "works for items that don't have a position yet" do
165
+ item.remove_from_list
166
+ item.insert_at 1
167
+ item[position_field].should == 1
168
+ end
169
+ end
170
+
171
+ context "to a higher position" do
172
+ let(:item) { category_1.items.order_by_position.first }
173
+
174
+ it "changes the item's position" do
175
+ item.insert_at 2
176
+ item[position_field].should == 2
177
+ end
178
+
179
+ it "shuffles intermediary positions" do
180
+ positions = category_1.items.order_by_position.map(&position_field)
181
+ positions.should == [0,1,2]
182
+ item.insert_at 2
183
+ positions = category_1.items.order_by_position.map(&position_field)
184
+ positions.should == [0,1,2]
185
+ end
186
+
187
+ it "works for items that don't have a position yet" do
188
+ item.remove_from_list
189
+ item.insert_at 2
190
+ item[position_field].should == 2
191
+ end
192
+ end
193
+
194
+ context "to the same position" do
195
+ it "does nothing" do
196
+ item = category_1.items.first
197
+ lambda do
198
+ positions = category_1.items.order_by_position.map(&position_field)
199
+ positions.should == [0,1,2]
200
+ item.insert_at item[position_field]
201
+ positions = category_1.items.order_by_position.map(&position_field)
202
+ positions.should == [0,1,2]
203
+ end.should_not change(item, position_field)
204
+ end
205
+ end
206
+
207
+ context "to extreme positions" do
208
+ it "like 0" do
209
+ item = category_1.items.order_by_position.last
210
+
211
+ item.remove_from_list
212
+ item.insert_at 0
213
+
214
+ item[position_field].should == 0
215
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
216
+ end
217
+ it "like the last position" do
218
+ item = category_1.items.order_by_position.first
219
+
220
+ item.remove_from_list
221
+ item.insert_at 1
222
+
223
+ item[position_field].should == 1
224
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
225
+ end
226
+ it "like the next available position" do
227
+ item = category_1.items.order_by_position.first
228
+
229
+ item.remove_from_list
230
+ item.insert_at 2
231
+
232
+ item[position_field].should == 2
233
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
234
+ end
235
+ end
236
+ end
237
+
238
+ describe " #move" do
239
+ context ":to =>" do
240
+ context "an Integer" do
241
+ it "inserts at a given position" do
242
+ item = category_1.items.order_by_position.first
243
+ item.should_receive(:insert_at).with 2
244
+ item.move to: 2
245
+ end
246
+ end
247
+
248
+ context "a Symbol" do
249
+ [:start, :top, :end, :bottom].each do |destination|
250
+ it "moves to #{destination}" do
251
+ item = category_1.items.first
252
+ item.should_receive("move_to_#{destination}")
253
+ item.move to: destination
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ [:before, :above, :after, :below, :forward, :backward, :lower, :higher].each do |sym|
260
+ context "#{sym} =>" do
261
+ it "delegates to the right method" do
262
+ item = category_1.items.first
263
+ other_item = category_1.items.last
264
+ item.should_receive("move_#{sym}").with(other_item)
265
+ item.move(sym => other_item)
266
+ end
267
+ end
268
+ end
269
+
270
+ [:backwards, :higher].each do |sym|
271
+ context "#{sym}" do
272
+ it "delegates to the right method" do
273
+ item = category_1.items.last
274
+ item.should_receive("move_#{sym}").with()
275
+ item.move sym
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ [:top, :start].each do |sym|
282
+ describe "#move_to_#{sym}" do
283
+ it "#{sym} moves an item in list to the start of list" do
284
+ item = category_1.items.order_by_position.last
285
+ item.move to: sym
286
+ item[position_field].should == 0
287
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
288
+ end
289
+
290
+ it "#{sym} moves an item not in list to the start of list" do
291
+ item = category_1.items.order_by_position.last
292
+ item.remove_from_list
293
+ item.move to: sym
294
+ item[position_field].should == 0
295
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
296
+ end
297
+ end
298
+ end
299
+
300
+ [:end, :bottom].each do |sym|
301
+ describe "#move_to_#{sym}" do
302
+ it "#{sym} moves an item in list to the end of list" do
303
+ item = category_1.items.order_by_position.first
304
+ item.move to: sym
305
+ item[position_field].should == 2
306
+ category_1.reload.items.order_by_position.map(&position_field).should == [0,1,2]
307
+ end
308
+
309
+ it "#{sym} moves an item not in list to the end of list" do
310
+ item = category_1.items.order_by_position.first
311
+ item.remove_from_list
312
+ item.move to: sym
313
+ item[position_field].should == 2
314
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
315
+ end
316
+ end
317
+ end
318
+
319
+ [:forwards, :lower].each do |sym|
320
+ describe " #move_#{sym}" do
321
+ let(:method) { "move_#{sym}" }
322
+
323
+ context "for the last item of the list" do
324
+ let(:item) { category_1.items.order_by_position.last }
325
+
326
+ it "does not change the item's position" do
327
+ lambda do
328
+ item.send method
329
+ end.should_not change(item, position_field)
330
+ end
331
+
332
+ it "keeps items ordered" do
333
+ item.send method
334
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
335
+ end
336
+
337
+ it "returns false" do
338
+ item.send(method).should be_false
339
+ end
340
+ end
341
+
342
+ context "for any other item" do
343
+ let(:item) { category_1.items.order_by_position.first }
344
+
345
+ it "moves to the next position" do
346
+ lambda do
347
+ item.send method
348
+ end.should change(item, position_field).by(1)
349
+ end
350
+
351
+ it "moves to the nth next position" do
352
+ lambda do
353
+ item.send method, 2
354
+ end.should change(item, position_field).by(2)
355
+ end
356
+
357
+ it "moves to the end of the list if n is too high" do
358
+ lambda do
359
+ item.send method, 9
360
+ end.should change(item, position_field).by(2)
361
+ end
362
+
363
+ it "keeps items ordered" do
364
+ item.send method, 2
365
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
366
+ end
367
+
368
+ it "returns true" do
369
+ item.send(method).should be_true
370
+ end
371
+ end
372
+ end
373
+ end
374
+
375
+ [:backwards, :higher].each do |sym|
376
+ describe " #move_#{sym}" do
377
+ let(:method) { "move_#{sym}" }
378
+
379
+ context "for the first item of the list" do
380
+ let(:item) { category_1.items.order_by_position.first }
381
+
382
+ it "does not change the item's position" do
383
+ lambda do
384
+ item.send method
385
+ end.should_not change(item, position_field)
386
+ end
387
+
388
+ it "keeps items ordered" do
389
+ item.send method
390
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
391
+ end
392
+
393
+ it "returns false" do
394
+ item.send(method).should be_false
395
+ end
396
+ end
397
+
398
+ context "for any other item" do
399
+ let(:item) { category_1.items.order_by_position.last }
400
+
401
+ it "moves to the previous position" do
402
+ lambda do
403
+ item.send method
404
+ end.should change(item, position_field).by(-1)
405
+ end
406
+
407
+ it "moves to the nth previous position" do
408
+ lambda do
409
+ item.send method, 2
410
+ end.should change(item, position_field).by(-2)
411
+ end
412
+
413
+ it "moves to the end of the list if n is too high" do
414
+ lambda do
415
+ item.send method, 9
416
+ end.should change(item, position_field).by(-2)
417
+ end
418
+
419
+ it "keeps items ordered" do
420
+ item.send method
421
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
422
+ end
423
+
424
+ it "returns true" do
425
+ item.send(method).should be_true
426
+ end
427
+ end
428
+ end
429
+ end
430
+
431
+ [:before, :above].each do |sym|
432
+ describe " #move_#{sym}" do
433
+ before do
434
+ item.send("move_#{sym}", other_item)
435
+ end
436
+
437
+ context "towards the start" do
438
+ let(:other_item) { category_1.items.order_by_position.first }
439
+ let(:item) { category_1.items.order_by_position.last }
440
+
441
+ it "moves to the same position as other_item" do
442
+ item[position_field].should == 0
443
+ end
444
+
445
+ it "shifts other_item " do
446
+ other_item.reload[position_field].should == 1
447
+ end
448
+
449
+ it "shifts any item after that by 1" do
450
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
451
+ end
452
+ end
453
+ context "towards the end" do
454
+ let(:item) { category_1.items.order_by_position.first }
455
+ let(:other_item) { category_1.items.order_by_position.last }
456
+
457
+ it "moves to the same position as other_item" do
458
+ item[position_field].should == 1
459
+ end
460
+
461
+ it "shifts other_item " do
462
+ other_item[position_field].should == 2
463
+ end
464
+
465
+ it "shifts any item after that by 1" do
466
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
467
+ end
468
+ end
469
+ end
470
+ end
471
+
472
+ [:after, :below].each do |sym|
473
+ describe " #move_#{sym}" do
474
+ before do
475
+ item.send("move_#{sym}", other_item)
476
+ end
477
+
478
+ context "towards the start" do
479
+ let(:other_item) { category_1.items.order_by_position.first }
480
+ let(:item) { category_1.items.order_by_position.last }
481
+
482
+ it "moves to other_item's next position" do
483
+ item[position_field].should == 1
484
+ end
485
+
486
+ it "shifts any item before that by -1" do
487
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
488
+ end
489
+ end
490
+ context "towards the end" do
491
+ let(:item) { category_1.items.order_by_position.first }
492
+ let(:other_item) { category_1.items.order_by_position.last }
493
+
494
+ it "moves to the same position as other_item" do
495
+ item[position_field].should == 2
496
+ end
497
+
498
+ it "shifts any item before that by -1" do
499
+ category_1.items.order_by_position.map(&position_field).should == [0,1,2]
500
+ end
501
+ end
502
+ end
503
+ end
504
+
505
+ describe "#start_position_in_list" do
506
+ before do
507
+ @original_start = Mongoid::ActsAsList.configuration.start_list_at
508
+ end
509
+ after do
510
+ Mongoid::ActsAsList.configure {|c| c.start_list_at = @original_start}
511
+ end
512
+
513
+ it "is configurable" do
514
+ category_3.items.should be_empty
515
+ start = 1
516
+ Mongoid::ActsAsList.configure {|c| c.start_list_at = start}
517
+ item = category_3.items.create!
518
+ item[position_field].should == start
519
+ item = category_3.items.create!
520
+ item[position_field].should == start+1
521
+ end
522
+ end
523
+ end