mongoid_acts_as_list 0.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.
@@ -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