dm-is-list 0.9.11 → 0.10.0

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