dm-is-list 0.9.11 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,9 @@
1
- require 'pathname'
2
- require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
1
+ require 'spec_helper'
3
2
 
4
3
  if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
5
4
  describe 'DataMapper::Is::List' do
6
5
 
7
- class ::User
6
+ class User
8
7
  include DataMapper::Resource
9
8
 
10
9
  property :id, Serial
@@ -13,10 +12,10 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
13
12
  has n, :todos
14
13
  end
15
14
 
16
- class ::Todo
15
+ class Todo
17
16
  include DataMapper::Resource
18
17
 
19
- property :id, Serial
18
+ property :id, Serial
20
19
  property :title, String
21
20
 
22
21
  belongs_to :user
@@ -25,187 +24,1029 @@ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
25
24
  end
26
25
 
27
26
  before :each do
28
- User.auto_migrate!(:default)
29
- Todo.auto_migrate!(:default)
30
-
31
- u1 = User.create(:name => "Johnny")
32
- Todo.create(:user => u1, :title => "Write down what is needed in a list-plugin")
33
- Todo.create(:user => u1, :title => "Complete a temporary version of is-list")
34
- Todo.create(:user => u1, :title => "Squash bugs in nested-set")
27
+ User.auto_migrate!
28
+ Todo.auto_migrate!
35
29
 
36
- u2 = User.create(:name => "Freddy")
37
- Todo.create(:user => u2, :title => "Eat tasty cupcake")
38
- Todo.create(:user => u2, :title => "Procrastinate on paid work")
39
- Todo.create(:user => u2, :title => "Go to sleep")
30
+ @u1 = User.create(:name => 'Johnny')
31
+ Todo.create(:user => @u1, :title => 'Write down what is needed in a list-plugin')
32
+ Todo.create(:user => @u1, :title => 'Complete a temporary version of is-list')
33
+ Todo.create(:user => @u1, :title => 'Squash any reported bugs')
34
+ Todo.create(:user => @u1, :title => 'Make public and await public scrutiny')
35
+ Todo.create(:user => @u1, :title => 'Rinse and repeat')
36
+ @u2 = User.create(:name => 'Freddy')
37
+ Todo.create(:user => @u2, :title => 'Eat tasty cupcake')
38
+ Todo.create(:user => @u2, :title => 'Procrastinate on paid work')
39
+ Todo.create(:user => @u2, :title => 'Go to sleep')
40
+ end
40
41
 
42
+ ##
43
+ # Keep things DRY shortcut
44
+ #
45
+ # todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
46
+ #
47
+ # todo_list(:user => @u2, :order => [:id]).should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
48
+ #
49
+ def todo_list(options={})
50
+ options = { :user => @u1, :order => [:position] }.merge(options)
51
+ Todo.all(:user => options[:user], :order => options[:order]).map{ |a| [a.id, a.position] }
41
52
  end
42
53
 
43
- describe 'automatic positioning' do
44
- it 'should get the shadow variable of the last position' do
45
- repository(:default) do |repos|
46
- Todo.get(3).position=8
47
- Todo.get(3).dirty?.should == true
48
- Todo.get(3).attribute_dirty?(:position).should == true
49
- Todo.get(3).original_values[:position].should == 3
50
- Todo.get(3).list_scope.should == Todo.get(3).original_list_scope
54
+ describe "Class Methods" do
55
+
56
+ describe "#repair_list" do
57
+
58
+ it "should repair a scoped list" do
59
+ DataMapper.repository(:default) do |repos|
60
+ items = Todo.all(:user => @u1, :order => [:position])
61
+ items.each{ |item| item.update(:position => [4,2,8,32,16][item.id - 1]) }
62
+ todo_list.should == [ [2, 2], [1, 4], [3, 8], [5, 16], [4, 32] ]
63
+
64
+ Todo.repair_list(:user_id => @u1.id)
65
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [5, 4], [4, 5] ]
66
+ end
51
67
  end
52
- end
53
68
 
54
- it 'should insert items into the list automatically' do
55
- repository(:default) do |repos|
56
- Todo.get(3).position.should == 3
57
- Todo.get(6).position.should == 3
69
+ it "should repair unscoped lists" do
70
+ DataMapper.repository(:default) do |repos|
71
+ Todo.all.map { |t| [t.id, t.position] }.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 1], [7, 2], [8, 3] ]
72
+ Todo.repair_list
73
+ # note the order, repairs lists based on position
74
+ Todo.all.map { |t| [t.id, t.position] }.should == [ [1, 1], [2, 3], [3, 5], [4, 7], [5, 8], [6, 2], [7, 4], [8, 6] ]
75
+ Todo.all(:order => [:position]).map { |t| t.id }.should == [1, 6, 2, 7, 3, 8, 4, 5]
76
+ end
58
77
  end
59
- end
60
78
 
61
- it 'should rearrange items when setting position yourself' do
62
- repository(:default) do |repos|
63
- todo = Todo.get(2)
64
- todo.position = 1
65
- todo.save
79
+ end #/ #repair_list
80
+
81
+ end #/ Class Methods
82
+
83
+ describe "Instance Methods" do
84
+
85
+ describe "#move" do
86
+
87
+ describe ":higher" do
88
+
89
+ it "should move item :higher in list" do
90
+ DataMapper.repository(:default) do |repos|
91
+ Todo.get(2).move(:higher).should == true
92
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
93
+ end
94
+ end
95
+
96
+ it "should NOT move item :higher when first in list" do
97
+ DataMapper.repository(:default) do |repos|
98
+ Todo.get(1).move(:higher).should == false
99
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
100
+ end
101
+ end
102
+
103
+ end #/ :higher
104
+
105
+ describe ":lower" do
106
+
107
+ it "should move item :lower in list" do
108
+ DataMapper.repository(:default) do |repos|
109
+ Todo.get(2).move(:lower).should == true
110
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
111
+ end
112
+ end
113
+
114
+ it "should NOT move item :lower when last in list" do
115
+ DataMapper.repository(:default) do |repos|
116
+ Todo.get(5).move(:lower).should == false
117
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
118
+ end
119
+ end
120
+
121
+ end #/ :lower
122
+
123
+ describe ":up" do
124
+
125
+ it "should move item :up in list" do
126
+ DataMapper.repository(:default) do |repos|
127
+ Todo.get(2).move(:up).should == true
128
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
129
+ end
130
+ end
131
+
132
+ it "should NOT move item :up when first in list" do
133
+ DataMapper.repository(:default) do |repos|
134
+ Todo.get(1).move(:up).should == false
135
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
136
+ end
137
+ end
138
+
139
+ end #/ :up
140
+
141
+ describe ":down" do
142
+
143
+ it "should move item :down in list" do
144
+ DataMapper.repository(:default) do |repos|
145
+ Todo.get(2).move(:down).should == true
146
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
147
+ end
148
+ end
149
+
150
+ it "should NOT move :down when last in list" do
151
+ DataMapper.repository(:default) do |repos|
152
+ Todo.get(5).move(:down).should == false
153
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
154
+ end
155
+ end
156
+
157
+ end #/ :down
158
+
159
+ describe ":top" do
160
+
161
+ it "should move item to :top of list" do
162
+ DataMapper.repository(:default) do |repos|
163
+ Todo.get(5).move(:top).should == true
164
+ todo_list.should == [ [5, 1], [1, 2], [2, 3], [3, 4], [4, 5] ]
165
+ end
166
+ end
167
+
168
+ it "should NOT move item to :top of list when it is already first" do
169
+ DataMapper.repository(:default) do |repos|
170
+ Todo.get(1).move(:top).should == false
171
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
172
+ end
173
+ end
174
+
175
+ end #/ :top
176
+
177
+ describe ":bottom" do
178
+
179
+ it "should move item to :bottom of list" do
180
+ DataMapper.repository(:default) do |repos|
181
+ Todo.get(2).move(:bottom).should == true
182
+ todo_list.should == [ [1, 1], [3, 2], [4, 3], [5, 4], [2, 5] ]
183
+ end
184
+ end
185
+
186
+ it "should NOT move item to :bottom of list when it is already last" do
187
+ DataMapper.repository(:default) do |repos|
188
+ Todo.get(5).move(:bottom).should == false
189
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
190
+ end
191
+ end
192
+
193
+ end #/ :bottom
194
+
195
+ describe ":highest" do
196
+
197
+ it "should move item :highest in list" do
198
+ DataMapper.repository(:default) do |repos|
199
+ Todo.get(5).move(:highest).should == true
200
+ todo_list.should == [ [5, 1], [1, 2], [2, 3], [3, 4], [4, 5] ]
201
+ end
202
+ end
203
+
204
+ it "should NOT move item :highest in list when it is already first" do
205
+ DataMapper.repository(:default) do |repos|
206
+ Todo.get(1).move(:highest).should == false
207
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
208
+ end
209
+ end
210
+
211
+ end #/ :highest
212
+
213
+ describe ":lowest" do
214
+
215
+ it "should move item :lowest in list" do
216
+ DataMapper.repository(:default) do |repos|
217
+ Todo.get(2).move(:lowest).should == true
218
+ todo_list.should == [ [1, 1], [3, 2], [4, 3], [5, 4], [2, 5] ]
219
+ end
220
+ end
221
+
222
+ it "should NOT move item :lowest in list when it is already last" do
223
+ DataMapper.repository(:default) do |repos|
224
+ Todo.get(5).move(:lowest).should == false
225
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
226
+ end
227
+ end
228
+
229
+ end #/ :lowest
230
+
231
+ describe ":above" do
232
+
233
+ it "should move item :above another in list" do
234
+ DataMapper.repository(:default) do |repos|
235
+ Todo.get(3).move(:above => Todo.get(2) ).should == true
236
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
237
+ end
238
+ end
239
+
240
+ it "should NOT move item :above itself" do
241
+ DataMapper.repository(:default) do |repos|
242
+ Todo.get(1).move(:above => Todo.get(1) ).should == false
243
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
244
+ end
245
+ end
246
+
247
+ it "should NOT move item :above a lower item (it's already above)" do
248
+ DataMapper.repository(:default) do |repos|
249
+ Todo.get(1).move(:above => Todo.get(2) ).should == false
250
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
251
+ end
252
+ end
253
+
254
+ it "should NOT move the item :above another item in a different scope" do
255
+ DataMapper.repository(:default) do |repos|
256
+ Todo.get(1).move(:above => Todo.get(6) ).should == false
257
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
258
+ end
259
+ end
260
+
261
+ end #/ :above
262
+
263
+ describe ":below" do
264
+
265
+ it "should move item :below another in list" do
266
+ DataMapper.repository(:default) do |repos|
267
+ Todo.get(2).move(:below => Todo.get(3) ).should == true
268
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
269
+ end
270
+ end
271
+
272
+ it "should NOT move item :below itself" do
273
+ DataMapper.repository(:default) do |repos|
274
+ Todo.get(1).move(:below => Todo.get(1) ).should == false # is this logical ???
275
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
276
+ end
277
+ end
278
+
279
+ it "should NOT move item :below a higher item (it's already below)" do
280
+ DataMapper.repository(:default) do |repos|
281
+ Todo.get(5).move(:below => Todo.get(4) ).should == false # is this logical ???
282
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
283
+ end
284
+ end
285
+
286
+ it "should NOT move the item :below another item in a different scope" do
287
+ DataMapper.repository(:default) do |repos|
288
+ Todo.get(1).move(:below => Todo.get(6) ).should == false
289
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
290
+ end
291
+ end
292
+
293
+ end #/ :below
294
+
295
+ describe ":to" do
296
+
297
+ describe "=> FixNum" do
298
+
299
+ it "should move item to the position" do
300
+ DataMapper.repository(:default) do |repos|
301
+ Todo.get(2).move(:to => 3 ).should == true
302
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
303
+ end
304
+ end
305
+
306
+ it "should NOT move item to a position above the first item in list (negative position)" do
307
+ DataMapper.repository(:default) do |repos|
308
+ Todo.get(2).move(:to => -33 ).should == true
309
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
310
+ end
311
+ end
312
+
313
+ it "should NOT move item to a position below the last item in list (out of range - position)" do
314
+ DataMapper.repository(:default) do |repos|
315
+ Todo.get(2).move(:to => 33 ).should == true
316
+ todo_list.should == [ [1, 1], [3, 2], [4, 3], [5, 4], [2, 5] ]
317
+ end
318
+ end
319
+
320
+ end #/ => FixNum
321
+
322
+ describe "=> String" do
323
+
324
+ it "should move item to the position" do
325
+ DataMapper.repository(:default) do |repos|
326
+ Todo.get(2).move(:to => '3' ).should == true
327
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
328
+ end
329
+ end
330
+
331
+ it "should NOT move item to a position above the first item in list (negative position)" do
332
+ DataMapper.repository(:default) do |repos|
333
+ Todo.get(2).move(:to => '-33' ).should == true
334
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
335
+ end
336
+ end
337
+
338
+ it "should NOT move item to a position below the last item in list (out of range - position)" do
339
+ DataMapper.repository(:default) do |repos|
340
+ Todo.get(2).move(:to => '33' ).should == true
341
+ todo_list.should == [ [1, 1], [3, 2], [4, 3], [5, 4], [2, 5] ]
342
+ end
343
+ end
344
+
345
+ end #/ => String
346
+
347
+ end #/ :to
348
+
349
+ describe "X (position as Integer)" do
350
+
351
+ it "should move item to the position" do
352
+ DataMapper.repository(:default) do |repos|
353
+ Todo.get(2).move(3).should == true
354
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
355
+ end
356
+ end
357
+
358
+ it "should move the same item to different positions multiple times" do
359
+ DataMapper.repository(:default) do |repos|
360
+ item = Todo.get(2)
361
+
362
+ item.move(3).should == true
363
+ todo_list.should == [ [1, 1], [3, 2], [2, 3], [4, 4], [5, 5] ]
364
+
365
+ item.move(1).should == true
366
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
367
+ end
368
+ end
369
+
370
+ it 'should NOT move item to a position above the first item in list (negative position)' do
371
+ DataMapper.repository(:default) do |repos|
372
+ Todo.get(2).move(-33).should == true
373
+ todo_list.should == [ [2, 1], [1, 2], [3, 3], [4, 4], [5, 5] ]
374
+ end
375
+ end
376
+
377
+ it 'should NOT move item to a position below the last item in list (out of range - position)' do
378
+ DataMapper.repository(:default) do |repos|
379
+ Todo.get(2).move(33).should == true
380
+ todo_list.should == [ [1, 1], [3, 2], [4, 3], [5, 4], [2, 5] ]
381
+ end
382
+ end
383
+
384
+ end #/ #move(X)
385
+
386
+ describe "#move(:non_existant_vector_symbol)" do
387
+
388
+ it "should raise an ArgumentError when given an un-recognised symbol value" do
389
+ DataMapper.repository(:default) do |repos|
390
+ lambda { Todo.get(2).move(:non_existant_vector_symbol) }.should raise_error(ArgumentError)
391
+ end
392
+ end
393
+
394
+ end #/ #move(:non_existant_vector)
395
+
396
+ end #/ #move
397
+
398
+ describe "#list_scope" do
399
+
400
+ describe 'without scope' do
401
+ class Property
402
+ include DataMapper::Resource
403
+
404
+ property :id, Serial
405
+
406
+ is :list
407
+ end
408
+
409
+ before do
410
+ @property = Property.new
411
+ end
412
+
413
+ it 'should return an empty Hash' do
414
+ DataMapper.repository(:default) do |repos|
415
+ @property.list_scope.should == {}
416
+ end
417
+ end
66
418
 
67
- Todo.get(2).position.should == 1
68
- Todo.get(1).position.should == 2
69
419
  end
70
- end
71
420
 
72
- it 'should rearrange items when setting the position yourself multiple times' do
73
- repository(:default) do |repos|
74
- todo = Todo.get(2)
75
- todo.position = 3
76
- todo.save
421
+ describe 'with scope' do
422
+
423
+ it 'should know the scope of the list the item belongs to' do
424
+ DataMapper.repository(:default) do |repos|
425
+ Todo.get(1).list_scope.should == {:user_id => @u1.id }
426
+ end
427
+ end
77
428
 
78
- Todo.get(2).position.should == 3
79
- Todo.get(3).position.should == 2
429
+ end
80
430
 
81
- todo = Todo.get(2)
82
- todo.position = 2
83
- todo.save
431
+ end #/ #list_scope
84
432
 
85
- Todo.get(2).position.should == 2
86
- Todo.get(3).position.should == 3
433
+ describe "#original_list_scope" do
434
+
435
+ it "should return a Hash" do
436
+ Todo.get(2).original_list_scope.class.should == Hash
87
437
  end
88
- end
89
- end
90
438
 
91
- describe 'movement' do
92
- it 'should rearrange items correctly when moving :higher' do
93
- repository(:default) do |repos|
94
- Todo.get(3).move :higher
95
- Todo.get(4).position.should == 1
96
- Todo.get(3).position.should == 2
97
- Todo.get(2).position.should == 3
439
+ it 'should know the original list scope after the scope changes' do
440
+ DataMapper.repository(:default) do |repos|
441
+ item = Todo.get(2)
442
+ item.user = @u2
443
+ item.original_list_scope.should == {:user_id => @u1.id }
444
+ item.original_list_scope.class.should == Hash
445
+ end
98
446
  end
99
- end
100
447
 
101
- it 'should rearrange items correctly when moving :lower' do
102
- repository(:default) do |repos|
103
- Todo.get(2).position.should == 2
104
- Todo.get(3).position.should == 3
105
- Todo.get(2).move :lower
106
- Todo.get(2).position.should == 3
107
- Todo.get(3).position.should == 2
448
+ end #/ #original_list_scope
108
449
 
109
- Todo.get(4).position.should == 1
450
+ describe "#list_query" do
451
+
452
+ it "should return a Hash" do
453
+ Todo.get(2).list_query.class.should == Hash
110
454
  end
111
- end
112
455
 
113
- it 'should rearrange items correctly when moving :highest or :lowest' do
114
- repository(:default) do |repos|
456
+ it 'should return a hash with conditions to get the entire list this item belongs to' do
457
+ DataMapper.repository(:default) do |repos|
458
+ Todo.get(2).list_query.should == { :user_id => @u1.id, :order => [:position] }
459
+ end
460
+ end
461
+
462
+ end #/ #list_query
115
463
 
116
- # list 1
117
- Todo.get(1).position.should == 1
118
- Todo.get(1).move(:lowest)
119
- Todo.get(1).position.should == 3
464
+ describe "#list" do
120
465
 
121
- # list 2
122
- Todo.get(6).position.should == 3
123
- Todo.get(6).move(:highest)
124
- Todo.get(6).position.should == 1
125
- Todo.get(5).position.should == 3
466
+ it "should returns a DataMapper::Collection object" do
467
+ Todo.get(2).list.class.should == DataMapper::Collection
468
+ Todo.get(2).list(:user => @u2 ).class.should == DataMapper::Collection
469
+ Todo.get(2).list(:user_id => nil).class.should == DataMapper::Collection
126
470
  end
127
- end
128
471
 
129
- it 'should not rearrange when trying to move top-item up, or bottom item down' do
130
- repository(:default) do |repos|
131
- Todo.get(4).position.should == 1
132
- Todo.get(4).move(:higher).should == false
133
- Todo.get(4).position.should == 1
472
+ it "should return all list items in the current list item's scope" do
473
+ DataMapper.repository(:default) do |repos|
474
+ Todo.get(2).list.should == Todo.all(:user => @u1)
475
+ end
476
+ end
134
477
 
135
- Todo.get(6).position.should == 3
136
- Todo.get(6).move(:lower).should == false
478
+ it "should return all items in the specified scope" do
479
+ DataMapper.repository(:default) do |repos|
480
+ Todo.get(2).list(:user => @u2 ).should == Todo.all(:user => @u2)
481
+ end
137
482
  end
138
- end
139
483
 
140
- it 'should rearrange items correctly when moving :above or :below' do
141
- repository(:default) do |repos|
142
- Todo.get(4).position.should == 1
143
- Todo.get(6).position.should == 3
144
- Todo.get(4).move(:below => Todo.get(6))
145
- Todo.get(4).position.should == 3
146
- Todo.get(6).position.should == 2
484
+ end #/ #list
485
+
486
+ describe "#repair_list" do
487
+
488
+ it 'should repair the list positions after a manually updated position' do
489
+ DataMapper.repository(:default) do |repos|
490
+ item = Todo.get(5)
491
+ item.update(:position => 20)
492
+ item.position.should == 20
493
+
494
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 20] ]
495
+
496
+ item = Todo.get(5)
497
+ item.repair_list
498
+ item.position.should == 5
499
+ end
147
500
  end
148
- end
149
- end
150
501
 
151
- describe 'scoping' do
152
- it 'should detach from old list if scope changed' do
153
- repository(:default) do |repos|
154
- item = Todo.get(4)
155
- item.position.should == 1
156
- item.user_id = 1
157
- item.save
502
+ it 'should repair the list positions in a really messed up list while retaining the order' do
503
+ DataMapper.repository(:default) do |repos|
504
+ # need to set these fixed in order to test the outcome
505
+ new_positions = [ 83, 5, 186, 48, 99 ]
506
+ Todo.all.each do |item|
507
+ item.update(:position => new_positions[item.id-1] )
508
+ end
509
+ # note the order of item id's
510
+ todo_list.should == [ [2, 5], [4, 48], [1, 83], [5, 99], [3, 186] ]
158
511
 
159
- item.list_scope.should != item.original_list_scope
160
- item.position.should == 1
161
- Todo.get(1).position.should == 2
162
- Todo.get(5).position.should == 1
512
+ item = Todo.get(5)
513
+ item.repair_list
514
+ # note position numbers being 1 - 5, and it retained the id positions
515
+ todo_list.should == [ [2, 1], [4, 2], [1, 3], [5, 4], [3, 5] ]
516
+ end
517
+ end
163
518
 
164
- item.user_id = 2
165
- item.position = 1
166
- item.save
519
+ end #/ #repair_list
167
520
 
168
- item.position.should == 1
169
- Todo.get(5).position.should == 2
521
+ describe "#reorder_list" do
170
522
 
523
+ before do
524
+ @u3 = User.create(:name => 'Eve')
525
+ @todo_1 = Todo.create(:user => @u3, :title => "Clean the house")
526
+ @todo_2 = Todo.create(:user => @u3, :title => "Brush the dogs")
527
+ @todo_3 = Todo.create(:user => @u3, :title => "Arrange bookshelf")
171
528
  end
172
- end
173
529
 
174
- it 'should not allow you to move item into another scope' do
175
- repository(:default) do |repos|
176
- item = Todo.get(1)
177
- item.position.should == 1
178
- item.move(:below => Todo.get(5)).should == false
530
+ it "should reorder the list based on the order options given" do
531
+ DataMapper.repository(:default) do |repos|
532
+ todo_list(:user => @u3).should == [ [9, 1], [10, 2], [11, 3] ]
533
+ @todo_1.reorder_list([:title.asc]).should == true
534
+ todo_list(:user => @u3).should == [ [11, 1], [10, 2], [9, 3] ]
535
+
536
+ @todo_1.reorder_list([:title.desc]).should == true
537
+ todo_list(:user => @u3).should == [ [9, 1], [10, 2], [11, 3] ]
538
+ end
179
539
  end
180
- end
181
540
 
182
- it 'should detach from list when deleted' do
183
- repository(:default) do |repos|
184
- item = Todo.get(4)
185
- item.position.should == 1
186
- Todo.get(5).position.should == 2
187
- item.destroy
541
+ end #/ #reorder_list
542
+
543
+ describe "#detach" do
544
+
545
+ it 'should detach from list and retain scope' do
546
+ DataMapper.repository(:default) do |repos|
547
+ item = Todo.get(2)
548
+ item.position.should == 2
549
+ item.user.should == @u1
188
550
 
189
- Todo.get(5).position.should == 1
551
+ item.detach
190
552
 
553
+ item.original_list_scope.should == { :user_id => @u1.id }
554
+ item.list_scope.should == { :user_id => @u1.id }
555
+ # item.list_scope.should != item.original_list_scope # FAIL. accepts both != and ==
556
+ item.position.should == nil
557
+
558
+ todo_list.should == [[1, 1], [2,nil], [3, 2], [4, 3], [5, 4]]
559
+ end
560
+ end
561
+
562
+ it 'should detach from list and change scope' do
563
+ DataMapper.repository(:default) do |repos|
564
+ item = Todo.get(2)
565
+ item.position.should == 2
566
+ item.user.should == @u1
567
+
568
+ item.detach(:user_id => 1)
569
+
570
+ item.original_list_scope.should == { :user_id => @u1.id }
571
+ item.list_scope.should == { :user_id => @u1.id }
572
+ # item.list_scope.should != item.original_list_scope # FAIL. accepts both != and ==
573
+ item.position.should == nil
574
+
575
+ todo_list.should == [[1, 1], [2,nil], [3, 2], [4, 3], [5, 4]]
576
+ end
191
577
  end
578
+
579
+ end #/ #detach
580
+
581
+ describe "#move_to_list" do
582
+
583
+ it "should move an item from one list to the bottom of another list" do
584
+ DataMapper.repository(:default) do |repos|
585
+ item = Todo.get(2)
586
+ list_scope = Todo.get(6).list_scope
587
+ list_scope.should == { :user_id => @u2.id }
588
+
589
+ item.move_to_list(@u2.id)
590
+ # equivalent of:
591
+ # item.detach
592
+ # item.user = @u2
593
+ # item.save
594
+ # item.reload
595
+
596
+ todo_list(:user => @u1).should == [ [1, 1], [3, 2], [4, 3], [5, 4] ]
597
+
598
+ todo_list(:user => @u2).should == [ [6, 1], [7, 2], [8, 3], [2, 4] ]
599
+ end
600
+ end
601
+
602
+ it "should move an item from one list to a fixed position in another list" do
603
+ pending %Q{Failing Test: Error = [ no such table: todos ]. Error is due to the nested transactions. (See notes in spec)}
604
+ # NOTE:: This error happens because of the nested transactions taking place
605
+ # first within the #move_to_list and then the #move method.
606
+ # If you comment out either of those transactions, the test passes.
607
+
608
+ DataMapper.repository(:default) do |repos|
609
+ item = Todo.get(2)
610
+ list_scope = Todo.get(6).list_scope
611
+ list_scope.should == { :user_id => @u2.id }
612
+
613
+ item.move_to_list(@u2.id, 3)
614
+
615
+ todo_list(:user => @u1).should == [ [1, 1], [3, 2], [4, 3], [5, 4] ]
616
+
617
+ todo_list(:user => @u2).should == [ [6, 1], [7, 2], [2, 3], [8, 4] ]
618
+ end
619
+ end
620
+
621
+ end #/ #move_to_list
622
+
623
+ describe "#left_sibling (alias #higher_item or #previous_item)" do
624
+
625
+ it "should return the higher item in list" do
626
+ DataMapper.repository(:default) do |repos|
627
+ item = Todo.get(2)
628
+ item.left_sibling.should == Todo.get(1)
629
+ item.higher_item.should == Todo.get(1)
630
+ item.previous_item.should == Todo.get(1)
631
+ end
632
+ end
633
+
634
+ it "should return nil when there's NO higher item" do
635
+ DataMapper.repository(:default) do |repos|
636
+ item = Todo.get(1)
637
+ item.left_sibling.should == nil
638
+ item.higher_item.should == nil
639
+ item.previous_item.should == nil
640
+ end
641
+ end
642
+
643
+ end #/ #left_sibling (alias #higher_item)
644
+
645
+ describe "#right_sibling (alias #lower_item or #next_item)" do
646
+
647
+ it "should return the lower item in list" do
648
+ DataMapper.repository(:default) do |repos|
649
+ item = Todo.get(2)
650
+ item.right_sibling.should == Todo.get(3)
651
+ item.lower_item.should == Todo.get(3)
652
+ item.next_item.should == Todo.get(3)
653
+ end
654
+ end
655
+
656
+ it "should return nil when there's NO lower item" do
657
+ DataMapper.repository(:default) do |repos|
658
+ item = Todo.get(5)
659
+ item.right_sibling.should == nil
660
+ item.lower_item.should == nil
661
+ item.next_item.should == nil
662
+ end
663
+ end
664
+
665
+ end #/ #right_sibling (alias #lower_item)
666
+
667
+ end #/ Instance Methods
668
+
669
+ describe "Workflows" do
670
+
671
+ describe "CRUD" do
672
+
673
+ # describe "#create" do
674
+ # pending
675
+ # end #/ #create
676
+
677
+ describe "Updating list items" do
678
+
679
+ it "should NOT loose position when updating other attributes" do
680
+ DataMapper.repository(:default) do |repos|
681
+ item = Todo.get(2)
682
+ item.position.should == 2
683
+ item.user.should == @u1
684
+
685
+ item.update(:title => "Updated")
686
+
687
+ item = Todo.get(2)
688
+ item.position.should == 2
689
+ item.title.should == 'Updated'
690
+ item.user.should == @u1
691
+ end
692
+ end
693
+
694
+ end #/ Updating list items
695
+
696
+ describe "Deleting items" do
697
+
698
+ describe "using #destroy" do
699
+
700
+ it 'should remove from list and old list should automatically repair positions' do
701
+ DataMapper.repository(:default) do |repos|
702
+ todo_list.should == [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
703
+ Todo.get(2).destroy.should == true
704
+ todo_list.should == [[1, 1], [3, 2], [4, 3], [5, 4] ]
705
+ end
706
+ end
707
+
708
+ end #/ using #destroy
709
+
710
+ describe "using #destroy!" do
711
+
712
+ it 'should remove from list and old list does NOT automatically repair positions' do
713
+ DataMapper.repository(:default) do |repos|
714
+ todo_list.should == [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
715
+ Todo.get(2).destroy!.should == true
716
+ todo_list.should == [[1, 1], [3, 3], [4, 4], [5, 5] ]
717
+ end
718
+ end
719
+
720
+ end #/ using #destroy!
721
+
722
+ end #/ Deleting items
723
+
724
+ end #/ CRUD
725
+
726
+ describe 'Automatic positioning' do
727
+
728
+ it 'should get the shadow variable of the last position' do
729
+ DataMapper.repository do
730
+ Todo.get(3).position = 8
731
+ Todo.get(3).should be_dirty
732
+ Todo.get(3).attribute_dirty?(:position).should == true
733
+ Todo.get(3).original_attributes[ Todo.properties[:position] ].should == 3
734
+ Todo.get(3).list_scope.should == Todo.get(3).original_list_scope
735
+ end
736
+ end
737
+
738
+ it 'should insert items into the list automatically on create' do
739
+ DataMapper.repository(:default) do |repos|
740
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
741
+ todo_list(:user => @u2).should == [ [6, 1], [7, 2], [8, 3] ]
742
+ end
743
+ end
744
+
745
+ end # automatic positioning
746
+
747
+ describe "Manual Positioning" do
748
+ # NOTE:: The positions in the list does NOT change automatically when an item is given
749
+ # a position via this syntax:
750
+ #
751
+ # item.position = 4
752
+ # item.save
753
+ #
754
+ # Enabling this functionality (re-shuffling list on update) causes a lot of extra SQL queries
755
+ # and ultimately still get the list order wrong when doing a batch update.
756
+ #
757
+ # This 'breaks' the common assumption of updating an item variable, but I think it's a worthwhile break
758
+
759
+ it 'should NOT rearrange items when setting position manually' do
760
+ DataMapper.repository(:default) do |repos|
761
+ item = Todo.get(2)
762
+ item.position = 1
763
+ item.save
764
+
765
+ todo_list.should == [ [1, 1], [2, 1], [3, 3], [4, 4], [5, 5] ] # note, the two items with 1 as position
766
+
767
+ item.update(:position => 3)
768
+ todo_list.should == [ [1, 1], [2, 3], [3, 3], [4, 4], [5, 5] ] # note, the two items with 3 as position
769
+ end
770
+ end
771
+
772
+ it 'should NOT rearrange items when setting position manually via update()' do
773
+ DataMapper.repository(:default) do |repos|
774
+ item = Todo.get(2)
775
+ item.update(:position => 1)
776
+
777
+ todo_list.should == [ [1, 1], [2, 1], [3, 3], [4, 4], [5, 5] ] # note, the two items with 1 as position
778
+
779
+ item.update(:position => 3)
780
+ todo_list.should == [ [1, 1], [2, 3], [3, 3], [4, 4], [5, 5] ] # note, the two items with 3 as position
781
+ end
782
+
783
+ end
784
+
785
+ end #/ Manual Positioning
786
+
787
+
788
+ describe "Batch change item positions" do
789
+
790
+ describe "when using item.position = N syntax " do
791
+
792
+ it "should reverse the list" do
793
+ DataMapper.repository(:default) do |repos|
794
+ items = Todo.all(:user => @u1, :order => [:position])
795
+ items.each{ |item| item.update(:position => [5,4,3,2,1].index(item.id) + 1) }
796
+ todo_list.should == [ [5,1], [4,2], [3,3], [2,4], [1,5] ]
797
+ end
798
+ end
799
+
800
+ it "should move the first item to last in list" do
801
+ DataMapper.repository(:default) do |repos|
802
+ items = Todo.all(:user => @u1, :order => [:position])
803
+
804
+ items.each{ |item| item.update(:position => [2,3,4,5,1].index(item.id) + 1) }
805
+
806
+ todo_list.should == [ [2,1], [3,2], [4,3], [5,4], [1,5] ]
807
+ end
808
+ end
809
+
810
+ it "should randomly move items around in the list" do
811
+ DataMapper.repository(:default) do |repos|
812
+ items = Todo.all(:user => @u1, :order => [:position])
813
+
814
+ items.each{ |item| item.update(:position => [5,2,4,3,1].index(item.id) + 1) }
815
+
816
+ todo_list.should == [ [5,1], [2,2], [4,3], [3,4], [1,5] ]
817
+ end
818
+ end
819
+
820
+ end #/ when using item.position = N syntax
821
+
822
+ describe "when using item.move(N) syntax => [NB! create more SQL queries]" do
823
+
824
+ it "should reverse the list => [NB! creates 5x the number of SQL queries]" do
825
+ DataMapper.repository(:default) do |repos|
826
+ items = Todo.all(:user => @u1, :order => [:position])
827
+
828
+ items.each{ |item| item.move([5,4,3,2,1].index(item.id) + 1) }
829
+
830
+ todo_list.should == [ [5,1], [4,2], [3,3], [2,4], [1,5] ]
831
+ end
832
+ end
833
+
834
+ it "should move the first item to last in list" do
835
+ DataMapper.repository(:default) do |repos|
836
+ items = Todo.all(:user => @u1, :order => [:position])
837
+
838
+ items.each{ |item| item.move([2,3,4,5,1].index(item.id) + 1) }
839
+
840
+ todo_list.should == [ [2,1], [3,2], [4,3], [5,4], [1,5] ]
841
+ end
842
+ end
843
+
844
+ it "should randomly move items around in the list" do
845
+ DataMapper.repository(:default) do |repos|
846
+ items = Todo.all(:user => @u1, :order => [:position])
847
+
848
+ items.each{ |item| item.move([5,2,4,3,1].index(item.id) + 1) }
849
+
850
+ todo_list.should == [ [5,1], [2,2], [4,3], [3,4], [1,5] ]
851
+ end
852
+ end
853
+
854
+ end #/ when using item.move(N) syntax
855
+
856
+ end #/ Re-ordering
857
+
858
+ describe "Movements" do
859
+
860
+ it "see the Instance Methods > #move specs above" do
861
+ # NOTE:: keeping this in the specs since this group was here previously, but it's now redundant.
862
+ # Should the tests be shared and used twice ?
863
+ true.should == true
864
+ end
865
+
866
+ end #/ Movements
867
+
868
+ describe "Scoping" do
869
+
870
+ it 'should detach from old list if scope is changed and retain position in new list' do
871
+ DataMapper.repository(:default) do |repos|
872
+ item = Todo.get(2)
873
+ item.position.should == 2
874
+ item.user.should == @u1
875
+
876
+ item.user = @u2
877
+ item.save
878
+
879
+ item.list_scope.should != item.original_list_scope
880
+ item.list_scope.should == { :user_id => @u2.id }
881
+ item.position.should == 2
882
+
883
+ todo_list.should == [[1, 1], [3, 2], [4, 3], [5, 4]]
884
+
885
+ todo_list(:user => @u2).should == [[6, 1], [2, 2], [7, 3], [8, 4]]
886
+ end
887
+ end
888
+
889
+ it 'should detach from old list if scope is changed and given bottom position in new list if position is empty' do
890
+ DataMapper.repository(:default) do |repos|
891
+ item = Todo.get(2)
892
+ item.position.should == 2
893
+ item.user.should == @u1
894
+
895
+ item.position = nil # NOTE:: Creates a messed up original list.
896
+ item.user = @u2
897
+ item.save
898
+
899
+ item.list_scope.should != item.original_list_scope
900
+ item.list_scope.should == { :user_id => @u2.id }
901
+ item.position.should == 4
902
+
903
+ todo_list.should == [ [1, 1], [3, 3], [4, 4], [5, 5] ] # messed up
904
+
905
+ todo_list(:user => @u2).should == [ [6, 1], [7, 2], [8, 3], [2, 4] ]
906
+ end
907
+ end
908
+
909
+ describe "when deleting item" do
910
+ # see Workflows > CRUD > Deleting items
911
+ end #/ when deleting item
912
+
913
+ end #/ Scoping
914
+
915
+ describe "STI inheritance" do
916
+
917
+ it "should have some tests" do
918
+ pending
919
+ end
920
+
921
+ end #/ STI inheritance
922
+
923
+ end #/ Workflows
924
+
925
+
926
+ describe "Twilight Zone" do
927
+
928
+ # NOTE:: I do not understand the reasons for this behaviour, but perhaps it's how it should be.
929
+ # Why does having two variables pointing to the same row prevent it from being updated ?
930
+ #
931
+ describe "accessing the same object via two variables" do
932
+
933
+ before do
934
+ @todo5 = Todo.get(5)
935
+ end
936
+
937
+ it "should NOT update list" do
938
+ DataMapper.repository(:default) do |repos|
939
+ item = Todo.get(5)
940
+ item.position.should == 5
941
+ item.position.should == @todo5.position
942
+
943
+ @todo5.update(:position => 20) # this should update the position in the DB
944
+
945
+ @todo5.position.should == 20
946
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
947
+
948
+ item.position.should == 5
949
+ end
950
+ end
951
+
952
+ it "should update list when doing item.reload" do
953
+ DataMapper.repository(:default) do |repos|
954
+ item = Todo.get(5)
955
+ item.position.should == 5
956
+ item.position.should == @todo5.position
957
+
958
+ @todo5.update(:position => 20) # this should update the position in the DB
959
+
960
+ @todo5.position.should == 20
961
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 5] ]
962
+
963
+ item.reload
964
+ item.position.should == 20
965
+
966
+ todo_list.should == [ [1, 1], [2, 2], [3, 3], [4, 4], [5, 20] ]
967
+ end
968
+ end
969
+
970
+ end #/ accessing the same object via two variables
971
+
972
+ end #/ Twilight Zone
973
+
974
+
975
+ describe "With :unique_index => position defined" do
976
+
977
+ class Client
978
+ include DataMapper::Resource
979
+
980
+ property :id, Serial
981
+ property :name, String
982
+
983
+ has n, :client_todos
192
984
  end
193
- end
194
985
 
195
- describe 'reparation' do
196
- it 'should fix them lists' do
197
- repository(:default) do |repos|
198
- # Need to do this with a batch update, as setting position = 20 wont
199
- # work (it will set it to bottom of list, not more)
200
- Todo.all(:position => 3).update!(:position => 20)
986
+ class ClientTodo
987
+ include DataMapper::Resource
988
+
989
+ property :id, Serial
990
+ property :title, String
991
+ property :position, Integer, :nullable => false, :unique_index => :position
992
+ property :client_id, Integer, :unique_index => :position
993
+
994
+ belongs_to :client
995
+
996
+ is :list, :scope => [:client_id]
997
+ end
201
998
 
202
- item = Todo.get(6)
203
- item.position.should == 20
204
- item.repair_list
205
- item.position.should == 3
999
+ before :each do
1000
+ Client.auto_migrate!
1001
+ ClientTodo.auto_migrate!
1002
+
1003
+ @u1 = Client.create(:name => 'Johnny')
1004
+ @u2 = Client.create(:name => 'Freddy')
1005
+ end
1006
+
1007
+ before(:each) do
1008
+ @loop = 20
1009
+ @loop.times do |n|
1010
+ ClientTodo.create(:client => @u1, :title => "ClientTodo #{n+1}" )
206
1011
  end
207
1012
  end
208
- end
1013
+
1014
+ describe "should handle :unique_index => :position" do
1015
+
1016
+ it "should generate all in the correct order" do
1017
+ DataMapper.repository(:default) do |repos|
1018
+ ClientTodo.all.map{ |a| [a.id, a.position] }.should == (1..@loop).map { |n| [n,n] }
1019
+ end
1020
+ end
1021
+
1022
+ it "should move items :higher in list" do
1023
+ pending "Failing Test: Error = [ columns position, client_id are not unique ] (See notes in spec)"
1024
+ # NOTE:: This error happens because of the :unique_index => position setting.
1025
+ # Most likely the reason is due to the order of updates to the position attribute in the DB is NOT
1026
+ # fully consistant, and clashes therefore occur.
1027
+ # This could be solved(?) with adding an 'ORDER BY position' in the SQL for MySQL, but won't work with SQLite3
1028
+ #
1029
+ # Commenting out :unique_index => :position in the ClientTodo model enables the tests to pass.
1030
+
1031
+ DataMapper.repository(:default) do |repos|
1032
+ ClientTodo.get(2).move(:higher).should == true
1033
+ ClientTodo.all.map{ |a| [a.id, a.position] }.should == [ [1, 2], [2, 1] ] + (3..@loop).map { |n| [n,n] }
1034
+ end
1035
+ end
1036
+
1037
+ it "should move items :lower in list" do
1038
+ pending "Failing Test: Error = [ columns position, client_id are not unique ] (See notes in spec)"
1039
+ DataMapper.repository(:default) do |repos|
1040
+ ClientTodo.get(9).move(:lower).should == true
1041
+ ClientTodo.all.map{ |a| [a.id, a.position] }.should == (1..8).map { |n| [n,n] } + [ [9, 10], [10, 9] ] + (11..@loop).map { |n| [n,n] }
1042
+ end
1043
+ end
1044
+
1045
+ end #/ should handle :unique_index => :position
1046
+
1047
+ end #/ With :unique_index => position defined
1048
+
209
1049
 
210
1050
  end
1051
+
211
1052
  end