mongoid_orderable 5.2.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +73 -58
  3. data/LICENSE.txt +20 -20
  4. data/README.md +256 -150
  5. data/Rakefile +24 -21
  6. data/lib/config/locales/en.yml +12 -9
  7. data/lib/mongoid/orderable.rb +29 -20
  8. data/lib/mongoid/orderable/configs/field_config.rb +79 -0
  9. data/lib/mongoid/orderable/configs/global_config.rb +26 -0
  10. data/lib/mongoid/orderable/engine.rb +204 -0
  11. data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -15
  12. data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
  13. data/lib/mongoid/orderable/generators/base.rb +21 -0
  14. data/lib/mongoid/orderable/generators/helpers.rb +29 -0
  15. data/lib/mongoid/orderable/generators/listable.rb +41 -0
  16. data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
  17. data/lib/mongoid/orderable/generators/movable.rb +62 -0
  18. data/lib/mongoid/orderable/generators/position.rb +26 -0
  19. data/lib/mongoid/orderable/generators/scope.rb +26 -0
  20. data/lib/mongoid/orderable/installer.rb +63 -0
  21. data/lib/mongoid/orderable/mixins/callbacks.rb +29 -0
  22. data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
  23. data/lib/mongoid/orderable/mixins/listable.rb +49 -0
  24. data/lib/mongoid/orderable/mixins/movable.rb +60 -0
  25. data/lib/mongoid/orderable/version.rb +7 -0
  26. data/lib/mongoid_orderable.rb +29 -56
  27. data/spec/mongoid/orderable_spec.rb +1486 -1380
  28. data/spec/spec_helper.rb +21 -21
  29. metadata +44 -41
  30. data/.gitignore +0 -4
  31. data/.rspec +0 -2
  32. data/.rubocop.yml +0 -6
  33. data/.rubocop_todo.yml +0 -88
  34. data/.rvmrc +0 -1
  35. data/.travis.yml +0 -48
  36. data/CONTRIBUTING.md +0 -118
  37. data/Dangerfile +0 -1
  38. data/Gemfile +0 -26
  39. data/RELEASING.md +0 -68
  40. data/lib/mongoid/orderable/callbacks.rb +0 -79
  41. data/lib/mongoid/orderable/configuration.rb +0 -58
  42. data/lib/mongoid/orderable/errors.rb +0 -2
  43. data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -6
  44. data/lib/mongoid/orderable/generator.rb +0 -33
  45. data/lib/mongoid/orderable/generator/helpers.rb +0 -27
  46. data/lib/mongoid/orderable/generator/listable.rb +0 -39
  47. data/lib/mongoid/orderable/generator/movable.rb +0 -60
  48. data/lib/mongoid/orderable/generator/position.rb +0 -24
  49. data/lib/mongoid/orderable/generator/scope.rb +0 -17
  50. data/lib/mongoid/orderable/helpers.rb +0 -49
  51. data/lib/mongoid/orderable/listable.rb +0 -47
  52. data/lib/mongoid/orderable/movable.rb +0 -56
  53. data/lib/mongoid/orderable/orderable_class.rb +0 -47
  54. data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
  55. data/lib/mongoid_orderable/version.rb +0 -3
  56. data/mongoid_orderable.gemspec +0 -26
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Orderable
5
+ module Mixins
6
+ module Movable
7
+ def move_to!(target_position, options = {})
8
+ move_field_to target_position, options
9
+ save
10
+ end
11
+ alias insert_at! move_to!
12
+
13
+ def move_to(target_position, options = {})
14
+ move_field_to target_position, options
15
+ end
16
+ alias insert_at move_to
17
+
18
+ def move_to=(target_position, options = {})
19
+ move_field_to target_position, options
20
+ end
21
+ alias insert_at= move_to=
22
+
23
+ %i[top bottom].each do |symbol|
24
+ class_eval <<~KLASS, __FILE__, __LINE__ + 1
25
+ def move_to_#{symbol}(options = {})
26
+ move_to :#{symbol}, options
27
+ end
28
+
29
+ def move_to_#{symbol}!(options = {})
30
+ move_to! :#{symbol}, options
31
+ end
32
+ KLASS
33
+ end
34
+
35
+ %i[higher lower].each do |symbol|
36
+ class_eval <<~KLASS, __FILE__, __LINE__ + 1
37
+ def move_#{symbol}(options = {})
38
+ move_to :#{symbol}, options
39
+ end
40
+
41
+ def move_#{symbol}!(options = {})
42
+ move_to! :#{symbol}, options
43
+ end
44
+ KLASS
45
+ end
46
+
47
+ protected
48
+
49
+ def move_all
50
+ @move_all || {}
51
+ end
52
+
53
+ def move_field_to(position, options)
54
+ field = options[:field] || default_orderable_field
55
+ @move_all = move_all.merge(field => position)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Orderable
5
+ VERSION = '6.0.0'
6
+ end
7
+ end
@@ -1,56 +1,29 @@
1
- require 'active_support'
2
-
3
- I18n.enforce_available_locales = false if I18n.respond_to?(:enforce_available_locales)
4
- I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
5
-
6
- require 'mongoid'
7
- require 'mongoid/compatibility'
8
-
9
- module MongoidOrderable
10
- if ::Mongoid::Compatibility::Version.mongoid3?
11
- def self.inc(instance, attribute, value)
12
- instance.inc attribute, value
13
- end
14
-
15
- def self.metadata(instance)
16
- instance.metadata
17
- end
18
- elsif ::Mongoid::Compatibility::Version.mongoid7?
19
- def self.inc(instance, attribute, value)
20
- instance.inc(attribute => value)
21
- end
22
-
23
- def self.metadata(instance)
24
- instance._association
25
- end
26
- else
27
- def self.inc(instance, attribute, value)
28
- instance.inc(attribute => value)
29
- end
30
-
31
- def self.metadata(instance)
32
- instance.relation_metadata
33
- end
34
- end
35
- end
36
-
37
- require 'mongoid_orderable/version'
38
-
39
- require 'mongoid_orderable/mongoid/contextual/memory'
40
-
41
- require 'mongoid/orderable'
42
- require 'mongoid/orderable/errors'
43
- require 'mongoid/orderable/configuration'
44
- require 'mongoid/orderable/helpers'
45
- require 'mongoid/orderable/callbacks'
46
- require 'mongoid/orderable/listable'
47
- require 'mongoid/orderable/movable'
48
-
49
- require 'mongoid/orderable/generator/listable'
50
- require 'mongoid/orderable/generator/movable'
51
- require 'mongoid/orderable/generator/position'
52
- require 'mongoid/orderable/generator/scope'
53
- require 'mongoid/orderable/generator/helpers'
54
- require 'mongoid/orderable/generator'
55
-
56
- require 'mongoid/orderable/orderable_class'
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ I18n.enforce_available_locales = false if I18n.respond_to?(:enforce_available_locales)
6
+ I18n.load_path << File.join(File.dirname(__FILE__), 'config', 'locales', 'en.yml')
7
+
8
+ require 'mongoid'
9
+
10
+ require 'mongoid/orderable/version'
11
+
12
+ require 'mongoid/orderable'
13
+ require 'mongoid/orderable/configs/global_config'
14
+ require 'mongoid/orderable/configs/field_config'
15
+ require 'mongoid/orderable/errors/invalid_target_position'
16
+ require 'mongoid/orderable/errors/transaction_failed'
17
+ require 'mongoid/orderable/mixins/helpers'
18
+ require 'mongoid/orderable/mixins/callbacks'
19
+ require 'mongoid/orderable/mixins/listable'
20
+ require 'mongoid/orderable/mixins/movable'
21
+ require 'mongoid/orderable/generators/base'
22
+ require 'mongoid/orderable/generators/listable'
23
+ require 'mongoid/orderable/generators/lock_collection'
24
+ require 'mongoid/orderable/generators/movable'
25
+ require 'mongoid/orderable/generators/position'
26
+ require 'mongoid/orderable/generators/scope'
27
+ require 'mongoid/orderable/generators/helpers'
28
+ require 'mongoid/orderable/engine'
29
+ require 'mongoid/orderable/installer'
@@ -1,1380 +1,1486 @@
1
- require 'spec_helper'
2
-
3
- describe Mongoid::Orderable do
4
- class SimpleOrderable
5
- include Mongoid::Document
6
- include Mongoid::Orderable
7
-
8
- orderable
9
- end
10
-
11
- class ScopedGroup
12
- include Mongoid::Document
13
-
14
- has_many :scoped_orderables
15
- has_many :multiple_columns_orderables
16
- end
17
-
18
- class ScopedOrderable
19
- include Mongoid::Document
20
- include Mongoid::Orderable
21
-
22
- field :group_id
23
- if ::Mongoid::Compatibility::Version.mongoid7?
24
- belongs_to :scoped_group, optional: true
25
- else
26
- belongs_to :scoped_group
27
- end
28
-
29
- orderable scope: :group
30
- end
31
-
32
- class StringScopedOrderable
33
- include Mongoid::Document
34
- include Mongoid::Orderable
35
-
36
- field :some_scope, type: Integer
37
-
38
- orderable scope: 'some_scope'
39
- end
40
-
41
- class EmbedsOrderable
42
- include Mongoid::Document
43
-
44
- embeds_many :embedded_orderables
45
- end
46
-
47
- class EmbeddedOrderable
48
- include Mongoid::Document
49
- include Mongoid::Orderable
50
-
51
- embedded_in :embeds_orderable
52
-
53
- orderable
54
- end
55
-
56
- class CustomizedOrderable
57
- include Mongoid::Document
58
- include Mongoid::Orderable
59
-
60
- orderable column: :pos, as: :my_position
61
- end
62
-
63
- class NoIndexOrderable
64
- include Mongoid::Document
65
- include Mongoid::Orderable
66
-
67
- orderable index: false
68
- end
69
-
70
- class ZeroBasedOrderable
71
- include Mongoid::Document
72
- include Mongoid::Orderable
73
-
74
- orderable base: 0
75
- end
76
-
77
- class Fruit
78
- include Mongoid::Document
79
- include Mongoid::Orderable
80
-
81
- orderable inherited: true
82
- end
83
-
84
- class Apple < Fruit
85
- end
86
-
87
- class Orange < Fruit
88
- end
89
-
90
- class ForeignKeyDiffersOrderable
91
- include Mongoid::Document
92
- include Mongoid::Orderable
93
-
94
- if ::Mongoid::Compatibility::Version.mongoid7?
95
- belongs_to :different_scope, class_name: 'ForeignKeyDiffersOrderable',
96
- foreign_key: 'different_orderable_id',
97
- optional: true
98
- else
99
- belongs_to :different_scope, class_name: 'ForeignKeyDiffersOrderable',
100
- foreign_key: 'different_orderable_id'
101
- end
102
-
103
- orderable scope: :different_scope
104
- end
105
-
106
- class MultipleColumnsOrderable
107
- include Mongoid::Document
108
- include Mongoid::Orderable
109
-
110
- field :group_id
111
-
112
- if ::Mongoid::Compatibility::Version.mongoid7?
113
- belongs_to :scoped_group, optional: true
114
- else
115
- belongs_to :scoped_group
116
- end
117
-
118
- orderable column: :pos, base: 0, index: false, as: :position
119
- orderable column: :serial_no, default: true
120
- orderable column: :groups, scope: :group
121
- end
122
-
123
- class MultipleScopedOrderable
124
- include Mongoid::Document
125
- include Mongoid::Orderable
126
-
127
- if ::Mongoid::Compatibility::Version.mongoid7?
128
- belongs_to :apple, optional: true
129
- belongs_to :orange, optional: true
130
- else
131
- belongs_to :apple
132
- belongs_to :orange
133
- end
134
-
135
- orderable column: :posa, scope: :apple_id
136
- orderable column: :poso, scope: :orange_id
137
- end
138
-
139
- describe SimpleOrderable do
140
- before :each do
141
- SimpleOrderable.delete_all
142
- 5.times do
143
- SimpleOrderable.create!
144
- end
145
- end
146
-
147
- def positions
148
- SimpleOrderable.all.map(&:position).sort
149
- end
150
-
151
- it 'should have proper position column' do
152
- expect(SimpleOrderable.fields.key?('position')).to be true
153
- expect(SimpleOrderable.fields['position'].options[:type]).to eq(Integer)
154
- end
155
-
156
- it 'should have index on position column' do
157
- if ::Mongoid::Compatibility::Version.mongoid3?
158
- expect(SimpleOrderable.index_options[{ position: 1 }]).not_to be_nil
159
- else
160
- expect(SimpleOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).not_to be_nil
161
- end
162
- end
163
-
164
- it 'should have a orderable base of 1' do
165
- expect(SimpleOrderable.create!.orderable_base).to eq(1)
166
- end
167
-
168
- it 'should set proper position while creation' do
169
- expect(positions).to eq([1, 2, 3, 4, 5])
170
- end
171
-
172
- describe 'removement' do
173
- it 'top' do
174
- SimpleOrderable.where(position: 1).destroy
175
- expect(positions).to eq([1, 2, 3, 4])
176
- end
177
-
178
- it 'bottom' do
179
- SimpleOrderable.where(position: 5).destroy
180
- expect(positions).to eq([1, 2, 3, 4])
181
- end
182
-
183
- it 'middle' do
184
- SimpleOrderable.where(position: 3).destroy
185
- expect(positions).to eq([1, 2, 3, 4])
186
- end
187
- end
188
-
189
- describe 'inserting' do
190
- it 'top' do
191
- newbie = SimpleOrderable.create! move_to: :top
192
- expect(positions).to eq([1, 2, 3, 4, 5, 6])
193
- expect(newbie.position).to eq(1)
194
- end
195
-
196
- it 'bottom' do
197
- newbie = SimpleOrderable.create! move_to: :bottom
198
- expect(positions).to eq([1, 2, 3, 4, 5, 6])
199
- expect(newbie.position).to eq(6)
200
- end
201
-
202
- it 'middle' do
203
- newbie = SimpleOrderable.create! move_to: 4
204
- expect(positions).to eq([1, 2, 3, 4, 5, 6])
205
- expect(newbie.position).to eq(4)
206
- end
207
-
208
- it 'middle (with a numeric string)' do
209
- newbie = SimpleOrderable.create! move_to: '4'
210
- expect(positions).to eq([1, 2, 3, 4, 5, 6])
211
- expect(newbie.position).to eq(4)
212
- end
213
-
214
- it 'middle (with a non-numeric string)' do
215
- expect do
216
- SimpleOrderable.create! move_to: 'four'
217
- end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
218
- end
219
- end
220
-
221
- describe 'movement' do
222
- it 'higher from top' do
223
- record = SimpleOrderable.where(position: 1).first
224
- record.update_attributes move_to: :higher
225
- expect(positions).to eq([1, 2, 3, 4, 5])
226
- expect(record.reload.position).to eq(1)
227
- end
228
-
229
- it 'higher from bottom' do
230
- record = SimpleOrderable.where(position: 5).first
231
- record.update_attributes move_to: :higher
232
- expect(positions).to eq([1, 2, 3, 4, 5])
233
- expect(record.reload.position).to eq(4)
234
- end
235
-
236
- it 'higher from middle' do
237
- record = SimpleOrderable.where(position: 3).first
238
- record.update_attributes move_to: :higher
239
- expect(positions).to eq([1, 2, 3, 4, 5])
240
- expect(record.reload.position).to eq(2)
241
- end
242
-
243
- it 'lower from top' do
244
- record = SimpleOrderable.where(position: 1).first
245
- record.update_attributes move_to: :lower
246
- expect(positions).to eq([1, 2, 3, 4, 5])
247
- expect(record.reload.position).to eq(2)
248
- end
249
-
250
- it 'lower from bottom' do
251
- record = SimpleOrderable.where(position: 5).first
252
- record.update_attributes move_to: :lower
253
- expect(positions).to eq([1, 2, 3, 4, 5])
254
- expect(record.reload.position).to eq(5)
255
- end
256
-
257
- it 'lower from middle' do
258
- record = SimpleOrderable.where(position: 3).first
259
- record.update_attributes move_to: :lower
260
- expect(positions).to eq([1, 2, 3, 4, 5])
261
- expect(record.reload.position).to eq(4)
262
- end
263
-
264
- it 'does nothing if position not change' do
265
- record = SimpleOrderable.where(position: 3).first
266
- record.save
267
- expect(positions).to eq([1, 2, 3, 4, 5])
268
- expect(record.reload.position).to eq(3)
269
- end
270
- end
271
-
272
- describe 'utiity methods' do
273
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
274
- record1 = SimpleOrderable.where(position: 1).first
275
- record2 = SimpleOrderable.where(position: 2).first
276
- record3 = SimpleOrderable.where(position: 3).first
277
- record4 = SimpleOrderable.where(position: 4).first
278
- record5 = SimpleOrderable.where(position: 5).first
279
- expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
280
- expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
281
- expect(record3.previous_items.to_a).to eq([record1, record2])
282
- expect(record3.next_items.to_a).to eq([record4, record5])
283
- end
284
- end
285
- end
286
-
287
- describe ScopedOrderable do
288
- before :each do
289
- ScopedOrderable.delete_all
290
- 2.times do
291
- ScopedOrderable.create! group_id: 1
292
- end
293
- 3.times do
294
- ScopedOrderable.create! group_id: 2
295
- end
296
- end
297
-
298
- def positions
299
- ScopedOrderable.order_by([:group_id, :asc], [:position, :asc]).map(&:position)
300
- end
301
-
302
- it 'should set proper position while creation' do
303
- expect(positions).to eq([1, 2, 1, 2, 3])
304
- end
305
-
306
- describe 'removement' do
307
- it 'top' do
308
- ScopedOrderable.where(position: 1, group_id: 1).destroy
309
- expect(positions).to eq([1, 1, 2, 3])
310
- end
311
-
312
- it 'bottom' do
313
- ScopedOrderable.where(position: 3, group_id: 2).destroy
314
- expect(positions).to eq([1, 2, 1, 2])
315
- end
316
-
317
- it 'middle' do
318
- ScopedOrderable.where(position: 2, group_id: 2).destroy
319
- expect(positions).to eq([1, 2, 1, 2])
320
- end
321
- end
322
-
323
- describe 'inserting' do
324
- it 'top' do
325
- newbie = ScopedOrderable.create! move_to: :top, group_id: 1
326
- expect(positions).to eq([1, 2, 3, 1, 2, 3])
327
- expect(newbie.position).to eq(1)
328
- end
329
-
330
- it 'bottom' do
331
- newbie = ScopedOrderable.create! move_to: :bottom, group_id: 2
332
- expect(positions).to eq([1, 2, 1, 2, 3, 4])
333
- expect(newbie.position).to eq(4)
334
- end
335
-
336
- it 'middle' do
337
- newbie = ScopedOrderable.create! move_to: 2, group_id: 2
338
- expect(positions).to eq([1, 2, 1, 2, 3, 4])
339
- expect(newbie.position).to eq(2)
340
- end
341
-
342
- it 'middle (with a numeric string)' do
343
- newbie = ScopedOrderable.create! move_to: '2', group_id: 2
344
- expect(positions).to eq([1, 2, 1, 2, 3, 4])
345
- expect(newbie.position).to eq(2)
346
- end
347
-
348
- it 'middle (with a non-numeric string)' do
349
- expect do
350
- ScopedOrderable.create! move_to: 'two', group_id: 2
351
- end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
352
- end
353
- end
354
-
355
- describe 'index' do
356
- it 'is not on position alone' do
357
- if ::Mongoid::Compatibility::Version.mongoid3?
358
- expect(ScopedOrderable.index_options[{ position: 1 }]).to be_nil
359
- else
360
- expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).to be_nil
361
- end
362
- end
363
-
364
- it 'is on compound fields' do
365
- if ::Mongoid::Compatibility::Version.mongoid3?
366
- expect(ScopedOrderable.index_options[{ group_id: 1, position: 1 }]).to_not be_nil
367
- else
368
- expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { group_id: 1, position: 1 } }).to_not be_nil
369
- end
370
- end
371
- end
372
-
373
- describe 'scope movement' do
374
- let(:record) { ScopedOrderable.where(group_id: 2, position: 2).first }
375
-
376
- it 'to a new scope group' do
377
- record.update_attributes group_id: 3
378
- expect(positions).to eq([1, 2, 1, 2, 1])
379
- expect(record.position).to eq(1)
380
- end
381
-
382
- context 'when moving to an existing scope group' do
383
- it 'without a position' do
384
- record.update_attributes group_id: 1
385
- expect(positions).to eq([1, 2, 3, 1, 2])
386
- expect(record.reload.position).to eq(3)
387
- end
388
-
389
- it 'with symbol position' do
390
- record.update_attributes group_id: 1, move_to: :top
391
- expect(positions).to eq([1, 2, 3, 1, 2])
392
- expect(record.reload.position).to eq(1)
393
- end
394
-
395
- it 'with point position' do
396
- record.update_attributes group_id: 1, move_to: 2
397
- expect(positions).to eq([1, 2, 3, 1, 2])
398
- expect(record.reload.position).to eq(2)
399
- end
400
-
401
- it 'with point position (with a numeric string)' do
402
- record.update_attributes group_id: 1, move_to: '2'
403
- expect(positions).to eq([1, 2, 3, 1, 2])
404
- expect(record.reload.position).to eq(2)
405
- end
406
-
407
- it 'with point position (with a non-numeric string)' do
408
- expect do
409
- record.update_attributes group_id: 1, move_to: 'two'
410
- end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
411
- end
412
- end
413
- end
414
-
415
- if defined?(Mongoid::IdentityMap)
416
-
417
- context 'when identity map is enabled' do
418
- let(:record) { ScopedOrderable.where(group_id: 2, position: 2).first }
419
-
420
- before do
421
- Mongoid.identity_map_enabled = true
422
- Mongoid::IdentityMap[ScopedOrderable.collection_name] = { record.id => record }
423
- end
424
-
425
- after do
426
- Mongoid.identity_map_enabled = false
427
- end
428
-
429
- it 'to a new scope group' do
430
- record.update_attributes group_id: 3
431
- expect(positions).to eq([1, 2, 1, 2, 1])
432
- expect(record.position).to eq(1)
433
- end
434
-
435
- it 'to an existing scope group' do
436
- record.update_attributes group_id: 1, move_to: 2
437
- expect(positions).to eq([1, 2, 3, 1, 2])
438
- expect(record.reload.position).to eq(2)
439
- end
440
-
441
- it 'to an existing scope group (with a numeric string)' do
442
- record.update_attributes group_id: 1, move_to: '2'
443
- expect(positions).to eq([1, 2, 3, 1, 2])
444
- expect(record.reload.position).to eq(2)
445
- end
446
-
447
- it 'to an existing scope group (with a non-numeric string)' do
448
- expect do
449
- record.update_attributes group_id: 1, move_to: 'two'
450
- end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
451
- end
452
- end
453
- end
454
-
455
- describe 'utility methods' do
456
- before do
457
- ScopedOrderable.delete_all
458
- 5.times { ScopedOrderable.create! }
459
- end
460
-
461
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
462
- record1 = ScopedOrderable.where(position: 1).first
463
- record2 = ScopedOrderable.where(position: 2).first
464
- record3 = ScopedOrderable.where(position: 3).first
465
- record4 = ScopedOrderable.where(position: 4).first
466
- record5 = ScopedOrderable.where(position: 5).first
467
- expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
468
- expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
469
- expect(record3.previous_items.to_a).to eq([record1, record2])
470
- expect(record3.next_items.to_a).to eq([record4, record5])
471
- # next_item & previous_item testing
472
- expect(record1.next_item).to eq(record2)
473
- expect(record2.previous_item).to eq(record1)
474
- expect(record1.previous_item).to eq(nil)
475
- expect(record5.next_item).to eq(nil)
476
- end
477
- end
478
- end
479
-
480
- describe StringScopedOrderable do
481
- it 'uses the foreign key of the relationship as scope' do
482
- orderable1 = StringScopedOrderable.create!(some_scope: 1)
483
- orderable2 = StringScopedOrderable.create!(some_scope: 1)
484
- orderable3 = StringScopedOrderable.create!(some_scope: 2)
485
- expect(orderable1.position).to eq 1
486
- expect(orderable2.position).to eq 2
487
- expect(orderable3.position).to eq 1
488
- end
489
- end
490
-
491
- describe EmbeddedOrderable do
492
- before :each do
493
- EmbedsOrderable.delete_all
494
- eo = EmbedsOrderable.create!
495
- 2.times do
496
- eo.embedded_orderables.create!
497
- end
498
- eo = EmbedsOrderable.create!
499
- 3.times do
500
- eo.embedded_orderables.create!
501
- end
502
- end
503
-
504
- def positions
505
- EmbedsOrderable.order_by(position: 1).all.map { |eo| eo.embedded_orderables.map(&:position).sort }
506
- end
507
-
508
- it 'sets proper position while creation' do
509
- expect(positions).to eq([[1, 2], [1, 2, 3]])
510
- end
511
-
512
- it 'moves an item returned by a query to position' do
513
- embedded_orderable1 = EmbedsOrderable.first.embedded_orderables.where(position: 1).first
514
- embedded_orderable2 = EmbedsOrderable.first.embedded_orderables.where(position: 2).first
515
- embedded_orderable1.move_to! 2
516
- expect(embedded_orderable2.reload.position).to eq(1)
517
- end
518
- end
519
-
520
- describe CustomizedOrderable do
521
- it 'does not have default position field' do
522
- expect(CustomizedOrderable.fields).not_to have_key('position')
523
- end
524
-
525
- it 'should have custom pos field' do
526
- expect(CustomizedOrderable.fields).to have_key('pos')
527
- end
528
-
529
- it 'should have an alias my_position which points to pos field on Mongoid 3+' do
530
- if CustomizedOrderable.respond_to?(:database_field_name)
531
- expect(CustomizedOrderable.database_field_name('my_position')).to eq('pos')
532
- end
533
- end
534
- end
535
-
536
- describe NoIndexOrderable do
537
- it 'should not have index on position column' do
538
- if ::Mongoid::Compatibility::Version.mongoid3?
539
- expect(NoIndexOrderable.index_options[[[:position, 1]]]).to be_nil
540
- else
541
- expect(NoIndexOrderable.index_specifications.detect { |spec| spec.key == :position }).to be_nil
542
- end
543
- end
544
- end
545
-
546
- describe ZeroBasedOrderable do
547
- before :each do
548
- ZeroBasedOrderable.delete_all
549
- 5.times do
550
- ZeroBasedOrderable.create!
551
- end
552
- end
553
-
554
- def positions
555
- ZeroBasedOrderable.all.map(&:position).sort
556
- end
557
-
558
- it 'should have a orderable base of 0' do
559
- expect(ZeroBasedOrderable.create!.orderable_base).to eq(0)
560
- end
561
-
562
- it 'should set proper position while creation' do
563
- expect(positions).to eq([0, 1, 2, 3, 4])
564
- end
565
-
566
- describe 'reset position' do
567
- before { ZeroBasedOrderable.update_all(position: nil) }
568
- it 'should properly reset position' do
569
- ZeroBasedOrderable.all.map(&:save)
570
- expect(positions).to eq([0, 1, 2, 3, 4])
571
- end
572
- end
573
-
574
- describe 'removement' do
575
- it 'top' do
576
- ZeroBasedOrderable.where(position: 0).destroy
577
- expect(positions).to eq([0, 1, 2, 3])
578
- end
579
-
580
- it 'bottom' do
581
- ZeroBasedOrderable.where(position: 4).destroy
582
- expect(positions).to eq([0, 1, 2, 3])
583
- end
584
-
585
- it 'middle' do
586
- ZeroBasedOrderable.where(position: 2).destroy
587
- expect(positions).to eq([0, 1, 2, 3])
588
- end
589
- end
590
-
591
- describe 'inserting' do
592
- it 'top' do
593
- newbie = ZeroBasedOrderable.create! move_to: :top
594
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
595
- expect(newbie.position).to eq(0)
596
- end
597
-
598
- it 'bottom' do
599
- newbie = ZeroBasedOrderable.create! move_to: :bottom
600
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
601
- expect(newbie.position).to eq(5)
602
- end
603
-
604
- it 'middle' do
605
- newbie = ZeroBasedOrderable.create! move_to: 3
606
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
607
- expect(newbie.position).to eq(3)
608
- end
609
-
610
- it 'middle (with a numeric string)' do
611
- newbie = ZeroBasedOrderable.create! move_to: '3'
612
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
613
- expect(newbie.position).to eq(3)
614
- end
615
-
616
- it 'middle (with a non-numeric string)' do
617
- expect do
618
- ZeroBasedOrderable.create! move_to: 'three'
619
- end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
620
- end
621
- end
622
-
623
- describe 'movement' do
624
- it 'higher from top' do
625
- record = ZeroBasedOrderable.where(position: 0).first
626
- record.update_attributes move_to: :higher
627
- expect(positions).to eq([0, 1, 2, 3, 4])
628
- expect(record.reload.position).to eq(0)
629
- end
630
-
631
- it 'higher from bottom' do
632
- record = ZeroBasedOrderable.where(position: 4).first
633
- record.update_attributes move_to: :higher
634
- expect(positions).to eq([0, 1, 2, 3, 4])
635
- expect(record.reload.position).to eq(3)
636
- end
637
-
638
- it 'higher from middle' do
639
- record = ZeroBasedOrderable.where(position: 3).first
640
- record.update_attributes move_to: :higher
641
- expect(positions).to eq([0, 1, 2, 3, 4])
642
- expect(record.reload.position).to eq(2)
643
- end
644
-
645
- it 'lower from top' do
646
- record = ZeroBasedOrderable.where(position: 0).first
647
- record.update_attributes move_to: :lower
648
- expect(positions).to eq([0, 1, 2, 3, 4])
649
- expect(record.reload.position).to eq(1)
650
- end
651
-
652
- it 'lower from bottom' do
653
- record = ZeroBasedOrderable.where(position: 4).first
654
- record.update_attributes move_to: :lower
655
- expect(positions).to eq([0, 1, 2, 3, 4])
656
- expect(record.reload.position).to eq(4)
657
- end
658
-
659
- it 'lower from middle' do
660
- record = ZeroBasedOrderable.where(position: 2).first
661
- record.update_attributes move_to: :lower
662
- expect(positions).to eq([0, 1, 2, 3, 4])
663
- expect(record.reload.position).to eq(3)
664
- end
665
-
666
- it 'does nothing if position not change' do
667
- record = ZeroBasedOrderable.where(position: 3).first
668
- record.save
669
- expect(positions).to eq([0, 1, 2, 3, 4])
670
- expect(record.reload.position).to eq(3)
671
- end
672
- end
673
-
674
- describe 'utiity methods' do
675
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
676
- record1 = SimpleOrderable.where(position: 1).first
677
- record2 = SimpleOrderable.where(position: 2).first
678
- record3 = SimpleOrderable.where(position: 3).first
679
- record4 = SimpleOrderable.where(position: 4).first
680
- record5 = SimpleOrderable.where(position: 5).first
681
- expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
682
- expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
683
- expect(record3.previous_items.to_a).to eq([record1, record2])
684
- expect(record3.next_items.to_a).to eq([record4, record5])
685
- # next_item & previous_item testing
686
- expect(record1.next_item).to eq(record2)
687
- expect(record2.previous_item).to eq(record1)
688
- expect(record1.previous_item).to eq(nil)
689
- expect(record5.next_item).to eq(nil)
690
- end
691
- end
692
- end
693
-
694
- describe Fruit do
695
- it 'should set proper position' do
696
- fruit1 = Apple.create
697
- fruit2 = Orange.create
698
- expect(fruit1.position).to eq(1)
699
- expect(fruit2.position).to eq(2)
700
- end
701
-
702
- describe 'movement' do
703
- before :each do
704
- Fruit.delete_all
705
- 5.times do
706
- Apple.create!
707
- end
708
- end
709
-
710
- it 'with symbol position' do
711
- first_apple = Apple.asc(:_id).first
712
- top_pos = first_apple.position
713
- bottom_pos = Apple.asc(:_id).last.position
714
- expect do
715
- first_apple.move_to! :bottom
716
- end.to change(first_apple, :position).from(top_pos).to bottom_pos
717
- end
718
-
719
- it 'with point position' do
720
- first_apple = Apple.asc(:_id).first
721
- top_pos = first_apple.position
722
- bottom_pos = Apple.asc(:_id).last.position
723
- expect do
724
- first_apple.move_to! bottom_pos
725
- end.to change(first_apple, :position).from(top_pos).to bottom_pos
726
- end
727
- end
728
-
729
- describe 'add orderable configurations in inherited class' do
730
- it 'does not affect the orderable configurations of parent class and sibling class' do
731
- class Apple
732
- orderable column: :serial
733
- end
734
- expect(Fruit.orderable_configurations).not_to eq Apple.orderable_configurations
735
- expect(Orange.orderable_configurations).not_to eq Apple.orderable_configurations
736
- expect(Fruit.orderable_configurations).to eq Orange.orderable_configurations
737
- end
738
- end
739
- end
740
-
741
- describe ForeignKeyDiffersOrderable do
742
- it 'uses the foreign key of the relationship as scope' do
743
- orderable1, orderable2, orderable3 = nil
744
- parent_scope1 = ForeignKeyDiffersOrderable.create
745
- parent_scope2 = ForeignKeyDiffersOrderable.create
746
- expect do
747
- orderable1 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope1)
748
- orderable2 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope1)
749
- orderable3 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope2)
750
- end.to_not raise_error
751
- expect(orderable1.position).to eq 1
752
- expect(orderable2.position).to eq 2
753
- expect(orderable3.position).to eq 1
754
- end
755
- end
756
-
757
- describe MultipleColumnsOrderable do
758
- before :each do
759
- MultipleColumnsOrderable.delete_all
760
- 5.times do
761
- MultipleColumnsOrderable.create!
762
- end
763
- end
764
-
765
- context 'default orderable' do
766
- let(:serial_nos) { MultipleColumnsOrderable.all.map(&:serial_no).sort }
767
-
768
- describe 'inserting' do
769
- let(:newbie) { MultipleColumnsOrderable.create! }
770
-
771
- before { @position = newbie.position }
772
-
773
- it 'top' do
774
- newbie.move_to! :top
775
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
776
- expect(newbie.serial_no).to eq(1)
777
- expect(newbie.position).to eq(@position)
778
- end
779
-
780
- it 'bottom' do
781
- newbie.move_to! :bottom
782
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
783
- expect(newbie.serial_no).to eq(6)
784
- expect(newbie.position).to eq(@position)
785
- end
786
-
787
- it 'middle' do
788
- newbie.move_to! 4
789
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
790
- expect(newbie.serial_no).to eq(4)
791
- expect(newbie.position).to eq(@position)
792
- end
793
- end
794
-
795
- describe 'movement' do
796
- it 'higher from top' do
797
- record = MultipleColumnsOrderable.where(serial_no: 1).first
798
- position = record.position
799
- record.move_higher!
800
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
801
- expect(record.serial_no).to eq(1)
802
- expect(record.position).to eq(position)
803
- end
804
-
805
- it 'higher from bottom' do
806
- record = MultipleColumnsOrderable.where(serial_no: 5).first
807
- position = record.position
808
- record.move_higher!
809
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
810
- expect(record.serial_no).to eq(4)
811
- expect(record.position).to eq(position)
812
- end
813
-
814
- it 'higher from middle' do
815
- record = MultipleColumnsOrderable.where(serial_no: 3).first
816
- position = record.position
817
- record.move_higher!
818
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
819
- expect(record.serial_no).to eq(2)
820
- expect(record.position).to eq(position)
821
- end
822
-
823
- it 'lower from top' do
824
- record = MultipleColumnsOrderable.where(serial_no: 1).first
825
- position = record.position
826
- record.move_lower!
827
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
828
- expect(record.serial_no).to eq(2)
829
- expect(record.position).to eq(position)
830
- end
831
-
832
- it 'lower from bottom' do
833
- record = MultipleColumnsOrderable.where(serial_no: 5).first
834
- position = record.position
835
- record.move_lower!
836
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
837
- expect(record.serial_no).to eq(5)
838
- expect(record.position).to eq(position)
839
- end
840
-
841
- it 'lower from middle' do
842
- record = MultipleColumnsOrderable.where(serial_no: 3).first
843
- position = record.position
844
- record.move_lower!
845
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
846
- expect(record.serial_no).to eq(4)
847
- expect(record.position).to eq(position)
848
- end
849
- end
850
-
851
- describe 'utility methods' do
852
- before do
853
- @record1 = MultipleColumnsOrderable.where(serial_no: 1).first
854
- @record2 = MultipleColumnsOrderable.where(serial_no: 2).first
855
- @record3 = MultipleColumnsOrderable.where(serial_no: 3).first
856
- @record4 = MultipleColumnsOrderable.where(serial_no: 4).first
857
- @record5 = MultipleColumnsOrderable.where(serial_no: 5).first
858
- end
859
-
860
- it 'should return the lower/higher item on the list for next_item/previous_item' do
861
- expect(@record1.next_item).to eq(@record2)
862
- expect(@record3.next_item).to eq(@record4)
863
- expect(@record5.next_item).to eq(nil)
864
- expect(@record1.prev_item).to eq(nil)
865
- expect(@record3.prev_item).to eq(@record2)
866
- expect(@record5.prev_item).to eq(@record4)
867
- end
868
-
869
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
870
- expect(@record1.next_items.to_a).to eq([@record2, @record3, @record4, @record5])
871
- expect(@record3.next_items.to_a).to eq([@record4, @record5])
872
- expect(@record5.next_items.to_a).to eq([])
873
- expect(@record1.previous_items.to_a).to eq([])
874
- expect(@record3.previous_items.to_a).to eq([@record1, @record2])
875
- expect(@record5.previous_items.to_a).to eq([@record1, @record2, @record3, @record4])
876
- end
877
- end
878
- end
879
-
880
- context 'serial_no orderable' do
881
- let(:serial_nos) { MultipleColumnsOrderable.all.map(&:serial_no).sort }
882
-
883
- it 'should have proper serial_no column' do
884
- expect(MultipleColumnsOrderable.fields.key?('serial_no')).to be true
885
- expect(MultipleColumnsOrderable.fields['serial_no'].options[:type]).to eq(Integer)
886
- end
887
-
888
- it 'should have index on serial_no column' do
889
- if ::Mongoid::Compatibility::Version.mongoid3?
890
- expect(MultipleColumnsOrderable.index_options[{ serial_no: 1 }]).not_to be_nil
891
- else
892
- expect(MultipleColumnsOrderable.index_specifications.detect { |spec| spec.key == { serial_no: 1 } }).not_to be_nil
893
- end
894
- end
895
-
896
- it 'should have a orderable base of 1' do
897
- expect(MultipleColumnsOrderable.first.orderable_base(:serial_no)).to eq(1)
898
- end
899
-
900
- it 'should set proper position while creation' do
901
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
902
- end
903
-
904
- describe 'removement' do
905
- it 'top' do
906
- MultipleColumnsOrderable.where(serial_no: 1).destroy
907
- expect(serial_nos).to eq([1, 2, 3, 4])
908
- end
909
-
910
- it 'bottom' do
911
- MultipleColumnsOrderable.where(serial_no: 5).destroy
912
- expect(serial_nos).to eq([1, 2, 3, 4])
913
- end
914
-
915
- it 'middle' do
916
- MultipleColumnsOrderable.where(serial_no: 3).destroy
917
- expect(serial_nos).to eq([1, 2, 3, 4])
918
- end
919
- end
920
-
921
- describe 'inserting' do
922
- let(:newbie) { MultipleColumnsOrderable.create! }
923
-
924
- before { @position = newbie.position }
925
-
926
- it 'top' do
927
- newbie.move_serial_no_to! :top
928
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
929
- expect(newbie.serial_no).to eq(1)
930
- expect(newbie.position).to eq(@position)
931
- end
932
-
933
- it 'bottom' do
934
- newbie.move_serial_no_to! :bottom
935
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
936
- expect(newbie.serial_no).to eq(6)
937
- expect(newbie.position).to eq(@position)
938
- end
939
-
940
- it 'middle' do
941
- newbie.move_serial_no_to! 4
942
- expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
943
- expect(newbie.serial_no).to eq(4)
944
- expect(newbie.position).to eq(@position)
945
- end
946
- end
947
-
948
- describe 'movement' do
949
- it 'higher from top' do
950
- record = MultipleColumnsOrderable.where(serial_no: 1).first
951
- position = record.position
952
- record.move_serial_no_higher!
953
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
954
- expect(record.serial_no).to eq(1)
955
- expect(record.position).to eq(position)
956
- end
957
-
958
- it 'higher from bottom' do
959
- record = MultipleColumnsOrderable.where(serial_no: 5).first
960
- position = record.position
961
- record.move_serial_no_higher!
962
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
963
- expect(record.serial_no).to eq(4)
964
- expect(record.position).to eq(position)
965
- end
966
-
967
- it 'higher from middle' do
968
- record = MultipleColumnsOrderable.where(serial_no: 3).first
969
- position = record.position
970
- record.move_serial_no_higher!
971
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
972
- expect(record.serial_no).to eq(2)
973
- expect(record.position).to eq(position)
974
- end
975
-
976
- it 'lower from top' do
977
- record = MultipleColumnsOrderable.where(serial_no: 1).first
978
- position = record.position
979
- record.move_serial_no_lower!
980
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
981
- expect(record.serial_no).to eq(2)
982
- expect(record.position).to eq(position)
983
- end
984
-
985
- it 'lower from bottom' do
986
- record = MultipleColumnsOrderable.where(serial_no: 5).first
987
- position = record.position
988
- record.move_serial_no_lower!
989
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
990
- expect(record.serial_no).to eq(5)
991
- expect(record.position).to eq(position)
992
- end
993
-
994
- it 'lower from middle' do
995
- record = MultipleColumnsOrderable.where(serial_no: 3).first
996
- position = record.position
997
- record.move_serial_no_lower!
998
- expect(serial_nos).to eq([1, 2, 3, 4, 5])
999
- expect(record.serial_no).to eq(4)
1000
- expect(record.position).to eq(position)
1001
- end
1002
- end
1003
-
1004
- describe 'utility methods' do
1005
- before do
1006
- @record1 = MultipleColumnsOrderable.where(serial_no: 1).first
1007
- @record2 = MultipleColumnsOrderable.where(serial_no: 2).first
1008
- @record3 = MultipleColumnsOrderable.where(serial_no: 3).first
1009
- @record4 = MultipleColumnsOrderable.where(serial_no: 4).first
1010
- @record5 = MultipleColumnsOrderable.where(serial_no: 5).first
1011
- end
1012
-
1013
- it 'should return the lower/higher item on the list for next_item/previous_item' do
1014
- expect(@record1.next_serial_no_item).to eq(@record2)
1015
- expect(@record3.next_serial_no_item).to eq(@record4)
1016
- expect(@record5.next_serial_no_item).to eq(nil)
1017
- expect(@record1.prev_serial_no_item).to eq(nil)
1018
- expect(@record3.prev_serial_no_item).to eq(@record2)
1019
- expect(@record5.prev_serial_no_item).to eq(@record4)
1020
- end
1021
-
1022
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1023
- expect(@record1.next_serial_no_items.to_a).to eq([@record2, @record3, @record4, @record5])
1024
- expect(@record3.next_serial_no_items.to_a).to eq([@record4, @record5])
1025
- expect(@record5.next_serial_no_items.to_a).to eq([])
1026
- expect(@record1.previous_serial_no_items.to_a).to eq([])
1027
- expect(@record3.previous_serial_no_items.to_a).to eq([@record1, @record2])
1028
- expect(@record5.previous_serial_no_items.to_a).to eq([@record1, @record2, @record3, @record4])
1029
- end
1030
- end
1031
- end
1032
-
1033
- context 'position orderable' do
1034
- let(:positions) { MultipleColumnsOrderable.all.map(&:position).sort }
1035
-
1036
- it 'should not have default position field' do
1037
- expect(MultipleColumnsOrderable.fields).not_to have_key('position')
1038
- end
1039
-
1040
- it 'should have custom pos field' do
1041
- expect(MultipleColumnsOrderable.fields).to have_key('pos')
1042
- expect(MultipleColumnsOrderable.fields['pos'].options[:type]).to eq(Integer)
1043
- end
1044
-
1045
- it 'should have index on position column' do
1046
- if ::Mongoid::Compatibility::Version.mongoid3?
1047
- expect(MultipleColumnsOrderable.index_options[{ position: 1 }]).to be_nil
1048
- else
1049
- expect(MultipleColumnsOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).to be_nil
1050
- end
1051
- end
1052
-
1053
- it 'should have a orderable base of 0' do
1054
- expect(MultipleColumnsOrderable.first.orderable_base(:position)).to eq(0)
1055
- end
1056
-
1057
- it 'should set proper position while creation' do
1058
- expect(positions).to eq([0, 1, 2, 3, 4])
1059
- end
1060
-
1061
- describe 'removement' do
1062
- it 'top' do
1063
- MultipleColumnsOrderable.where(pos: 1).destroy
1064
- expect(positions).to eq([0, 1, 2, 3])
1065
- end
1066
-
1067
- it 'bottom' do
1068
- MultipleColumnsOrderable.where(pos: 4).destroy
1069
- expect(positions).to eq([0, 1, 2, 3])
1070
- end
1071
-
1072
- it 'middle' do
1073
- MultipleColumnsOrderable.where(pos: 3).destroy
1074
- expect(positions).to eq([0, 1, 2, 3])
1075
- end
1076
- end
1077
-
1078
- describe 'inserting' do
1079
- let(:newbie) { MultipleColumnsOrderable.create! }
1080
-
1081
- before { @serial_no = newbie.serial_no }
1082
-
1083
- it 'top' do
1084
- newbie.move_position_to! :top
1085
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
1086
- expect(newbie.position).to eq(0)
1087
- expect(newbie.serial_no).to eq(@serial_no)
1088
- end
1089
-
1090
- it 'bottom' do
1091
- newbie.move_position_to! :bottom
1092
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
1093
- expect(newbie.position).to eq(5)
1094
- expect(newbie.serial_no).to eq(@serial_no)
1095
- end
1096
-
1097
- it 'middle' do
1098
- newbie.move_position_to! 4
1099
- expect(positions).to eq([0, 1, 2, 3, 4, 5])
1100
- expect(newbie.position).to eq(4)
1101
- expect(newbie.serial_no).to eq(@serial_no)
1102
- end
1103
- end
1104
-
1105
- describe 'movement' do
1106
- it 'higher from top' do
1107
- record = MultipleColumnsOrderable.where(pos: 0).first
1108
- position = record.serial_no
1109
- record.move_position_higher!
1110
- expect(positions).to eq([0, 1, 2, 3, 4])
1111
- expect(record.position).to eq(0)
1112
- expect(record.serial_no).to eq(position)
1113
- end
1114
-
1115
- it 'higher from bottom' do
1116
- record = MultipleColumnsOrderable.where(pos: 4).first
1117
- position = record.serial_no
1118
- record.move_position_higher!
1119
- expect(positions).to eq([0, 1, 2, 3, 4])
1120
- expect(record.position).to eq(3)
1121
- expect(record.serial_no).to eq(position)
1122
- end
1123
-
1124
- it 'higher from middle' do
1125
- record = MultipleColumnsOrderable.where(pos: 3).first
1126
- position = record.serial_no
1127
- record.move_position_higher!
1128
- expect(positions).to eq([0, 1, 2, 3, 4])
1129
- expect(record.position).to eq(2)
1130
- expect(record.serial_no).to eq(position)
1131
- end
1132
-
1133
- it 'lower from top' do
1134
- record = MultipleColumnsOrderable.where(pos: 0).first
1135
- position = record.serial_no
1136
- record.move_position_lower!
1137
- expect(positions).to eq([0, 1, 2, 3, 4])
1138
- expect(record.position).to eq(1)
1139
- expect(record.serial_no).to eq(position)
1140
- end
1141
-
1142
- it 'lower from bottom' do
1143
- record = MultipleColumnsOrderable.where(pos: 4).first
1144
- position = record.serial_no
1145
- record.move_position_lower!
1146
- expect(positions).to eq([0, 1, 2, 3, 4])
1147
- expect(record.position).to eq(4)
1148
- expect(record.serial_no).to eq(position)
1149
- end
1150
-
1151
- it 'lower from middle' do
1152
- record = MultipleColumnsOrderable.where(pos: 3).first
1153
- position = record.serial_no
1154
- record.move_position_lower!
1155
- expect(positions).to eq([0, 1, 2, 3, 4])
1156
- expect(record.position).to eq(4)
1157
- expect(record.serial_no).to eq(position)
1158
- end
1159
- end
1160
-
1161
- describe 'utility methods' do
1162
- before do
1163
- @record1 = MultipleColumnsOrderable.where(pos: 0).first
1164
- @record2 = MultipleColumnsOrderable.where(pos: 1).first
1165
- @record3 = MultipleColumnsOrderable.where(pos: 2).first
1166
- @record4 = MultipleColumnsOrderable.where(pos: 3).first
1167
- @record5 = MultipleColumnsOrderable.where(pos: 4).first
1168
- end
1169
-
1170
- it 'should return the lower/higher item on the list for next_item/previous_item' do
1171
- expect(@record1.next_position_item).to eq(@record2)
1172
- expect(@record3.next_position_item).to eq(@record4)
1173
- expect(@record5.next_position_item).to eq(nil)
1174
- expect(@record1.prev_position_item).to eq(nil)
1175
- expect(@record3.prev_position_item).to eq(@record2)
1176
- expect(@record5.prev_position_item).to eq(@record4)
1177
- end
1178
-
1179
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1180
- expect(@record1.next_position_items.to_a).to eq([@record2, @record3, @record4, @record5])
1181
- expect(@record3.next_position_items.to_a).to eq([@record4, @record5])
1182
- expect(@record5.next_position_items.to_a).to eq([])
1183
- expect(@record1.previous_position_items.to_a).to eq([])
1184
- expect(@record3.previous_position_items.to_a).to eq([@record1, @record2])
1185
- expect(@record5.previous_position_items.to_a).to eq([@record1, @record2, @record3, @record4])
1186
- end
1187
- end
1188
- end
1189
-
1190
- context 'group_count orderable' do
1191
- before :each do
1192
- MultipleColumnsOrderable.delete_all
1193
- 2.times { MultipleColumnsOrderable.create! group_id: 1 }
1194
- 3.times { MultipleColumnsOrderable.create! group_id: 2 }
1195
- end
1196
-
1197
- let(:all_groups) { MultipleColumnsOrderable.order_by([:group_id, :asc], [:groups, :asc]).map(&:groups) }
1198
-
1199
- it 'should set proper position while creation' do
1200
- expect(all_groups).to eq([1, 2, 1, 2, 3])
1201
- end
1202
-
1203
- describe 'removement' do
1204
- it 'top' do
1205
- MultipleColumnsOrderable.where(groups: 1, group_id: 1).destroy
1206
- expect(all_groups).to eq([1, 1, 2, 3])
1207
- end
1208
-
1209
- it 'bottom' do
1210
- MultipleColumnsOrderable.where(groups: 3, group_id: 2).destroy
1211
- expect(all_groups).to eq([1, 2, 1, 2])
1212
- end
1213
-
1214
- it 'middle' do
1215
- MultipleColumnsOrderable.where(groups: 2, group_id: 2).destroy
1216
- expect(all_groups).to eq([1, 2, 1, 2])
1217
- end
1218
- end
1219
-
1220
- describe 'inserting' do
1221
- it 'top' do
1222
- newbie = MultipleColumnsOrderable.create! group_id: 1
1223
- newbie.move_groups_to! :top
1224
- expect(all_groups).to eq([1, 2, 3, 1, 2, 3])
1225
- expect(newbie.groups).to eq(1)
1226
- end
1227
-
1228
- it 'bottom' do
1229
- newbie = MultipleColumnsOrderable.create! group_id: 2
1230
- newbie.move_groups_to! :bottom
1231
- expect(all_groups).to eq([1, 2, 1, 2, 3, 4])
1232
- expect(newbie.groups).to eq(4)
1233
- end
1234
-
1235
- it 'middle' do
1236
- newbie = MultipleColumnsOrderable.create! group_id: 2
1237
- newbie.move_groups_to! 2
1238
- expect(all_groups).to eq([1, 2, 1, 2, 3, 4])
1239
- expect(newbie.groups).to eq(2)
1240
- end
1241
- end
1242
-
1243
- describe 'scope movement' do
1244
- let(:record) { MultipleColumnsOrderable.where(group_id: 2, groups: 2).first }
1245
-
1246
- it 'to a new scope group' do
1247
- record.update_attributes group_id: 3
1248
- expect(all_groups).to eq([1, 2, 1, 2, 1])
1249
- expect(record.groups).to eq(1)
1250
- end
1251
-
1252
- context 'when moving to an existing scope group' do
1253
- it 'without a position' do
1254
- record.update_attributes group_id: 1
1255
- expect(all_groups).to eq([1, 2, 3, 1, 2])
1256
- expect(record.reload.groups).to eq(3)
1257
- end
1258
-
1259
- it 'with symbol position' do
1260
- record.update_attributes group_id: 1
1261
- record.move_groups_to! :top
1262
- expect(all_groups).to eq([1, 2, 3, 1, 2])
1263
- expect(record.reload.groups).to eq(1)
1264
- end
1265
-
1266
- it 'with point position' do
1267
- record.update_attributes group_id: 1
1268
- record.move_groups_to! 2
1269
- expect(all_groups).to eq([1, 2, 3, 1, 2])
1270
- expect(record.reload.groups).to eq(2)
1271
- end
1272
- end
1273
- end
1274
-
1275
- if defined?(Mongoid::IdentityMap)
1276
-
1277
- context 'when identity map is enabled' do
1278
- let(:record) { MultipleColumnsOrderable.where(group_id: 2, groups: 2).first }
1279
-
1280
- before do
1281
- Mongoid.identity_map_enabled = true
1282
- Mongoid::IdentityMap[MultipleColumnsOrderable.collection_name] = { record.id => record }
1283
- end
1284
-
1285
- after { Mongoid.identity_map_enabled = false }
1286
-
1287
- it 'to a new scope group' do
1288
- record.update_attributes group_id: 3
1289
- expect(all_groups).to eq([1, 2, 1, 2, 1])
1290
- expect(record.groups).to eq(1)
1291
- end
1292
-
1293
- it 'to an existing scope group' do
1294
- record.update_attributes group_id: 1
1295
- record.move_groups_to! 2
1296
- expect(all_groups).to eq([1, 2, 3, 1, 2])
1297
- expect(record.groups).to eq(2)
1298
- end
1299
- end
1300
- end
1301
-
1302
- describe 'utility methods' do
1303
- before do
1304
- @record1 = MultipleColumnsOrderable.where(group_id: 2, groups: 1).first
1305
- @record2 = MultipleColumnsOrderable.where(group_id: 2, groups: 2).first
1306
- @record3 = MultipleColumnsOrderable.where(group_id: 2, groups: 3).first
1307
- @record4 = MultipleColumnsOrderable.where(group_id: 1, groups: 1).first
1308
- @record5 = MultipleColumnsOrderable.where(group_id: 1, groups: 2).first
1309
- end
1310
-
1311
- it 'should return the lower/higher item on the list for next_item/previous_item' do
1312
- expect(@record1.next_groups_item).to eq(@record2)
1313
- expect(@record4.next_groups_item).to eq(@record5)
1314
- expect(@record3.next_groups_item).to eq(nil)
1315
- expect(@record1.prev_groups_item).to eq(nil)
1316
- expect(@record3.prev_groups_item).to eq(@record2)
1317
- expect(@record5.prev_groups_item).to eq(@record4)
1318
- end
1319
-
1320
- it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1321
- expect(@record1.next_groups_items.to_a).to eq([@record2, @record3])
1322
- expect(@record3.next_groups_items.to_a).to eq([])
1323
- expect(@record4.next_groups_items.to_a).to eq([@record5])
1324
- expect(@record1.previous_groups_items.to_a).to eq([])
1325
- expect(@record3.previous_groups_items.to_a).to eq([@record1, @record2])
1326
- expect(@record5.previous_groups_items.to_a).to eq([@record4])
1327
- end
1328
- end
1329
- end
1330
- end
1331
-
1332
- describe MultipleScopedOrderable do
1333
- before :each do
1334
- Apple.delete_all; Orange.delete_all
1335
- MultipleScopedOrderable.delete_all
1336
-
1337
- 3.times do
1338
- Apple.create; Orange.create
1339
- end
1340
-
1341
- MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
1342
- MultipleScopedOrderable.create! apple_id: 2, orange_id: 1
1343
- MultipleScopedOrderable.create! apple_id: 2, orange_id: 2
1344
- MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
1345
- MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
1346
- MultipleScopedOrderable.create! apple_id: 3, orange_id: 3
1347
- MultipleScopedOrderable.create! apple_id: 2, orange_id: 3
1348
- MultipleScopedOrderable.create! apple_id: 3, orange_id: 2
1349
- MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
1350
- end
1351
-
1352
- def apple_positions
1353
- MultipleScopedOrderable.order_by([:apple_id, :asc], [:posa, :asc]).map(&:posa)
1354
- end
1355
-
1356
- def orange_positions
1357
- MultipleScopedOrderable.order_by([:orange_id, :asc], [:poso, :asc]).map(&:poso)
1358
- end
1359
-
1360
- describe 'default positions' do
1361
- it { expect(apple_positions).to eq([1, 2, 3, 4, 1, 2, 3, 1, 2]) }
1362
- it { expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4]) }
1363
- end
1364
-
1365
- describe 'change the scope of the apple' do
1366
- let(:record) { MultipleScopedOrderable.first }
1367
- before do
1368
- record.update_attribute(:apple_id, 2)
1369
- end
1370
-
1371
- it 'should properly set the apple positions' do
1372
- expect(apple_positions).to eq([1, 2, 3, 1, 2, 3, 4, 1, 2])
1373
- end
1374
-
1375
- it 'should not affect the orange positions' do
1376
- expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4])
1377
- end
1378
- end
1379
- end
1380
- end
1
+ require 'spec_helper'
2
+
3
+ describe Mongoid::Orderable do
4
+ Mongoid::Orderable.configure do |c|
5
+ c.use_transactions = true
6
+ c.transaction_max_retries = 100
7
+ c.lock_collection = :foo_bar_locks
8
+ end
9
+
10
+ class SimpleOrderable
11
+ include Mongoid::Document
12
+ include Mongoid::Orderable
13
+
14
+ orderable
15
+ end
16
+
17
+ class ScopedGroup
18
+ include Mongoid::Document
19
+
20
+ has_many :scoped_orderables
21
+ has_many :multiple_fields_orderables
22
+ end
23
+
24
+ class ScopedOrderable
25
+ include Mongoid::Document
26
+ include Mongoid::Orderable
27
+
28
+ belongs_to :group, class_name: 'ScopedGroup', optional: true
29
+
30
+ orderable scope: :group
31
+ end
32
+
33
+ class StringScopedOrderable
34
+ include Mongoid::Document
35
+ include Mongoid::Orderable
36
+
37
+ field :some_scope, type: Integer
38
+
39
+ orderable scope: 'some_scope'
40
+ end
41
+
42
+ class EmbedsOrderable
43
+ include Mongoid::Document
44
+
45
+ embeds_many :embedded_orderables
46
+ end
47
+
48
+ class EmbeddedOrderable
49
+ include Mongoid::Document
50
+ include Mongoid::Orderable
51
+
52
+ embedded_in :embeds_orderable
53
+
54
+ orderable
55
+ end
56
+
57
+ class CustomizedOrderable
58
+ include Mongoid::Document
59
+ include Mongoid::Orderable
60
+
61
+ orderable field: :pos, as: :my_position
62
+ end
63
+
64
+ class NoIndexOrderable
65
+ include Mongoid::Document
66
+ include Mongoid::Orderable
67
+
68
+ orderable index: false
69
+ end
70
+
71
+ class ZeroBasedOrderable
72
+ include Mongoid::Document
73
+ include Mongoid::Orderable
74
+
75
+ orderable base: 0
76
+ end
77
+
78
+ class Fruit
79
+ include Mongoid::Document
80
+ include Mongoid::Orderable
81
+
82
+ orderable inherited: true
83
+ end
84
+
85
+ class Apple < Fruit
86
+ end
87
+
88
+ class Orange < Fruit
89
+ end
90
+
91
+ class ForeignKeyDiffersOrderable
92
+ include Mongoid::Document
93
+ include Mongoid::Orderable
94
+
95
+ belongs_to :different_scope, class_name: 'ForeignKeyDiffersOrderable',
96
+ foreign_key: 'different_orderable_id',
97
+ optional: true
98
+
99
+ orderable scope: :different_scope
100
+ end
101
+
102
+ class MultipleFieldsOrderable
103
+ include Mongoid::Document
104
+ include Mongoid::Orderable
105
+
106
+ belongs_to :group, class_name: 'ScopedGroup', optional: true
107
+
108
+ orderable field: :pos, base: 0, index: false, as: :position
109
+ orderable field: :serial_no, default: true
110
+ orderable field: :groups, scope: :group
111
+ end
112
+
113
+ class MultipleScopedOrderable
114
+ include Mongoid::Document
115
+ include Mongoid::Orderable
116
+
117
+ belongs_to :apple, optional: true
118
+ belongs_to :orange, optional: true
119
+
120
+ orderable field: :posa, scope: :apple_id
121
+ orderable field: :poso, scope: :orange_id
122
+ end
123
+
124
+ describe SimpleOrderable do
125
+ before :each do
126
+ 5.times { SimpleOrderable.create! }
127
+ end
128
+
129
+ def positions
130
+ SimpleOrderable.pluck(:position).sort
131
+ end
132
+
133
+ it 'should have proper position field' do
134
+ expect(SimpleOrderable.fields.key?('position')).to be true
135
+ expect(SimpleOrderable.fields['position'].options[:type]).to eq(Integer)
136
+ end
137
+
138
+ it 'should have index on position field' do
139
+ expect(SimpleOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).not_to be_nil
140
+ end
141
+
142
+ it 'should have a orderable base of 1' do
143
+ expect(SimpleOrderable.create!.orderable_top).to eq(1)
144
+ end
145
+
146
+ it 'should set proper position while creation' do
147
+ expect(positions).to eq([1, 2, 3, 4, 5])
148
+ end
149
+
150
+ describe 'removement' do
151
+ it 'top' do
152
+ SimpleOrderable.where(position: 1).destroy
153
+ expect(positions).to eq([1, 2, 3, 4])
154
+ end
155
+
156
+ it 'bottom' do
157
+ SimpleOrderable.where(position: 5).destroy
158
+ expect(positions).to eq([1, 2, 3, 4])
159
+ end
160
+
161
+ it 'middle' do
162
+ SimpleOrderable.where(position: 3).destroy
163
+ expect(positions).to eq([1, 2, 3, 4])
164
+ end
165
+ end
166
+
167
+ describe 'inserting' do
168
+ it 'top' do
169
+ newbie = SimpleOrderable.create! move_to: :top
170
+ expect(positions).to eq([1, 2, 3, 4, 5, 6])
171
+ expect(newbie.position).to eq(1)
172
+ end
173
+
174
+ it 'bottom' do
175
+ newbie = SimpleOrderable.create! move_to: :bottom
176
+ expect(positions).to eq([1, 2, 3, 4, 5, 6])
177
+ expect(newbie.position).to eq(6)
178
+ end
179
+
180
+ it 'middle' do
181
+ newbie = SimpleOrderable.create! move_to: 4
182
+ expect(positions).to eq([1, 2, 3, 4, 5, 6])
183
+ expect(newbie.position).to eq(4)
184
+ end
185
+
186
+ it 'middle (with a numeric string)' do
187
+ newbie = SimpleOrderable.create! move_to: '4'
188
+ expect(positions).to eq([1, 2, 3, 4, 5, 6])
189
+ expect(newbie.position).to eq(4)
190
+ end
191
+
192
+ it 'middle (with a non-numeric string)' do
193
+ expect do
194
+ SimpleOrderable.create! move_to: 'four'
195
+ end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
196
+ end
197
+
198
+ it 'simultaneous create and update' do
199
+ newbie = SimpleOrderable.new
200
+ newbie.send(:orderable_update_positions) { }
201
+ expect(newbie.position).to eq(6)
202
+ another = SimpleOrderable.create!
203
+ expect(another.position).to eq(6)
204
+ newbie.save!
205
+ expect(positions).to eq([1, 2, 3, 4, 5, 6, 7])
206
+ expect(newbie.position).to eq(7)
207
+ expect(another.position).to eq(6)
208
+ end
209
+
210
+ it 'parallel updates' do
211
+ newbie = SimpleOrderable.new
212
+ newbie.send(:orderable_update_positions) { }
213
+ another = SimpleOrderable.create!
214
+ newbie.save!
215
+ expect(positions).to eq([1, 2, 3, 4, 5, 6, 7])
216
+ expect(newbie.position).to eq(7)
217
+ expect(another.position).to eq(6)
218
+ end
219
+ end
220
+
221
+ describe 'movement' do
222
+ it 'higher from top' do
223
+ record = SimpleOrderable.where(position: 1).first
224
+ record.update_attributes move_to: :higher
225
+ expect(positions).to eq([1, 2, 3, 4, 5])
226
+ expect(record.reload.position).to eq(1)
227
+ end
228
+
229
+ it 'higher from bottom' do
230
+ record = SimpleOrderable.where(position: 5).first
231
+ record.update_attributes move_to: :higher
232
+ expect(positions).to eq([1, 2, 3, 4, 5])
233
+ expect(record.reload.position).to eq(4)
234
+ end
235
+
236
+ it 'higher from middle' do
237
+ record = SimpleOrderable.where(position: 3).first
238
+ record.update_attributes move_to: :higher
239
+ expect(positions).to eq([1, 2, 3, 4, 5])
240
+ expect(record.reload.position).to eq(2)
241
+ end
242
+
243
+ it 'lower from top' do
244
+ record = SimpleOrderable.where(position: 1).first
245
+ record.update_attributes move_to: :lower
246
+ expect(positions).to eq([1, 2, 3, 4, 5])
247
+ expect(record.reload.position).to eq(2)
248
+ end
249
+
250
+ it 'lower from bottom' do
251
+ record = SimpleOrderable.where(position: 5).first
252
+ record.update_attributes move_to: :lower
253
+ expect(positions).to eq([1, 2, 3, 4, 5])
254
+ expect(record.reload.position).to eq(5)
255
+ end
256
+
257
+ it 'lower from middle' do
258
+ record = SimpleOrderable.where(position: 3).first
259
+ record.update_attributes move_to: :lower
260
+ expect(positions).to eq([1, 2, 3, 4, 5])
261
+ expect(record.reload.position).to eq(4)
262
+ end
263
+
264
+ it 'does nothing if position not change' do
265
+ record = SimpleOrderable.where(position: 3).first
266
+ record.save
267
+ expect(positions).to eq([1, 2, 3, 4, 5])
268
+ expect(record.reload.position).to eq(3)
269
+ end
270
+ end
271
+
272
+ describe 'utility methods' do
273
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
274
+ record1 = SimpleOrderable.where(position: 1).first
275
+ record2 = SimpleOrderable.where(position: 2).first
276
+ record3 = SimpleOrderable.where(position: 3).first
277
+ record4 = SimpleOrderable.where(position: 4).first
278
+ record5 = SimpleOrderable.where(position: 5).first
279
+ expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
280
+ expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
281
+ expect(record3.previous_items.to_a).to eq([record1, record2])
282
+ expect(record3.next_items.to_a).to eq([record4, record5])
283
+ expect(record1.next_item).to eq(record2)
284
+ expect(record2.previous_item).to eq(record1)
285
+ expect(record1.previous_item).to eq(nil)
286
+ expect(record5.next_item).to eq(nil)
287
+ end
288
+ end
289
+
290
+ describe 'concurrency' do
291
+ it 'should correctly move items to top' do
292
+ 20.times.map do
293
+ Thread.new do
294
+ record = SimpleOrderable.all.sample
295
+ record.update_attributes move_to: :top
296
+ end
297
+ end.each(&:join)
298
+
299
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
300
+ end
301
+
302
+ it 'should correctly move items to bottom' do
303
+ 20.times.map do
304
+ Thread.new do
305
+ record = SimpleOrderable.all.sample
306
+ record.update_attributes move_to: :bottom
307
+ end
308
+ end.each(&:join)
309
+
310
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
311
+ end
312
+
313
+ it 'should correctly move items higher' do
314
+ 20.times.map do
315
+ Thread.new do
316
+ record = SimpleOrderable.all.sample
317
+ record.update_attributes move_to: :higher
318
+ end
319
+ end.each(&:join)
320
+
321
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
322
+ end
323
+
324
+ it 'should correctly move items lower' do
325
+ 20.times.map do
326
+ Thread.new do
327
+ record = SimpleOrderable.all.sample
328
+ record.update_attributes move_to: :lower
329
+ end
330
+ end.each(&:join)
331
+
332
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
333
+ end
334
+
335
+ it 'should correctly insert at the top' do
336
+ 20.times.map do
337
+ Thread.new do
338
+ SimpleOrderable.create!(move_to: :top)
339
+ end
340
+ end.each(&:join)
341
+
342
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
343
+ end
344
+
345
+ it 'should correctly insert at the bottom' do
346
+ 20.times.map do
347
+ Thread.new do
348
+ SimpleOrderable.create!
349
+ end
350
+ end.each(&:join)
351
+
352
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
353
+ end
354
+
355
+ it 'should correctly insert at a random position' do
356
+ 20.times.map do
357
+ Thread.new do
358
+ SimpleOrderable.create!(move_to: (1..10).to_a.sample)
359
+ end
360
+ end.each(&:join)
361
+
362
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
363
+ end
364
+
365
+ it 'should correctly move items to a random position' do
366
+ 20.times.map do
367
+ Thread.new do
368
+ record = SimpleOrderable.all.sample
369
+ record.update_attributes move_to: (1..5).to_a.sample
370
+ end
371
+ end.each(&:join)
372
+
373
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
374
+ end
375
+
376
+ context 'empty database' do
377
+ before { SimpleOrderable.delete_all }
378
+
379
+ it 'should correctly insert at the top' do
380
+ 20.times.map do
381
+ Thread.new do
382
+ SimpleOrderable.create!(move_to: :top)
383
+ end
384
+ end.each(&:join)
385
+
386
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
387
+ end
388
+
389
+ it 'should correctly insert at the bottom' do
390
+ 20.times.map do
391
+ Thread.new do
392
+ SimpleOrderable.create!
393
+ end
394
+ end.each(&:join)
395
+
396
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
397
+ end
398
+
399
+ it 'should correctly insert at a random position' do
400
+ 20.times.map do
401
+ Thread.new do
402
+ SimpleOrderable.create!(move_to: (1..10).to_a.sample)
403
+ end
404
+ end.each(&:join)
405
+
406
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
407
+ end
408
+ end
409
+ end
410
+ end
411
+
412
+ describe ScopedOrderable do
413
+ before :each do
414
+ 2.times { ScopedOrderable.create! group_id: 1 }
415
+ 3.times { ScopedOrderable.create! group_id: 2 }
416
+ end
417
+
418
+ def positions
419
+ ScopedOrderable.order_by([:group_id, :asc], [:position, :asc]).map(&:position)
420
+ end
421
+
422
+ it 'should set proper position while creation' do
423
+ expect(positions).to eq([1, 2, 1, 2, 3])
424
+ end
425
+
426
+ describe 'removement' do
427
+ it 'top' do
428
+ ScopedOrderable.where(position: 1, group_id: 1).destroy
429
+ expect(positions).to eq([1, 1, 2, 3])
430
+ end
431
+
432
+ it 'bottom' do
433
+ ScopedOrderable.where(position: 3, group_id: 2).destroy
434
+ expect(positions).to eq([1, 2, 1, 2])
435
+ end
436
+
437
+ it 'middle' do
438
+ ScopedOrderable.where(position: 2, group_id: 2).destroy
439
+ expect(positions).to eq([1, 2, 1, 2])
440
+ end
441
+ end
442
+
443
+ describe 'inserting' do
444
+ it 'top' do
445
+ newbie = ScopedOrderable.create! move_to: :top, group_id: 1
446
+ expect(positions).to eq([1, 2, 3, 1, 2, 3])
447
+ expect(newbie.position).to eq(1)
448
+ end
449
+
450
+ it 'bottom' do
451
+ newbie = ScopedOrderable.create! move_to: :bottom, group_id: 2
452
+ expect(positions).to eq([1, 2, 1, 2, 3, 4])
453
+ expect(newbie.position).to eq(4)
454
+ end
455
+
456
+ it 'middle' do
457
+ newbie = ScopedOrderable.create! move_to: 2, group_id: 2
458
+ expect(positions).to eq([1, 2, 1, 2, 3, 4])
459
+ expect(newbie.position).to eq(2)
460
+ end
461
+
462
+ it 'middle (with a numeric string)' do
463
+ newbie = ScopedOrderable.create! move_to: '2', group_id: 2
464
+ expect(positions).to eq([1, 2, 1, 2, 3, 4])
465
+ expect(newbie.position).to eq(2)
466
+ end
467
+
468
+ it 'middle (with a non-numeric string)' do
469
+ expect do
470
+ ScopedOrderable.create! move_to: 'two', group_id: 2
471
+ end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
472
+ end
473
+ end
474
+
475
+ describe 'index' do
476
+ it 'is not on position alone' do
477
+ expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).to be_nil
478
+ end
479
+
480
+ it 'is on compound fields' do
481
+ expect(ScopedOrderable.index_specifications.detect { |spec| spec.key == { group_id: 1, position: 1 } }).to_not be_nil
482
+ end
483
+ end
484
+
485
+ describe 'scope movement' do
486
+ let(:record) { ScopedOrderable.where(group_id: 2, position: 2).first }
487
+
488
+ it 'to a new scope group' do
489
+ record.update_attributes group_id: 3
490
+ expect(positions).to eq([1, 2, 1, 2, 1])
491
+ expect(record.position).to eq(1)
492
+ end
493
+
494
+ context 'when moving to an existing scope group' do
495
+ it 'without a position' do
496
+ record.update_attributes group_id: 1
497
+ expect(positions).to eq([1, 2, 3, 1, 2])
498
+ expect(record.reload.position).to eq(3)
499
+ end
500
+
501
+ it 'with symbol position' do
502
+ record.update_attributes group_id: 1, move_to: :top
503
+ expect(positions).to eq([1, 2, 3, 1, 2])
504
+ expect(record.reload.position).to eq(1)
505
+ end
506
+
507
+ it 'with point position' do
508
+ record.update_attributes group_id: 1, move_to: 2
509
+ expect(positions).to eq([1, 2, 3, 1, 2])
510
+ expect(record.reload.position).to eq(2)
511
+ end
512
+
513
+ it 'with point position (with a numeric string)' do
514
+ record.update_attributes group_id: 1, move_to: '2'
515
+ expect(positions).to eq([1, 2, 3, 1, 2])
516
+ expect(record.reload.position).to eq(2)
517
+ end
518
+
519
+ it 'with point position (with a non-numeric string)' do
520
+ expect do
521
+ record.update_attributes group_id: 1, move_to: 'two'
522
+ end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
523
+ end
524
+ end
525
+ end
526
+
527
+ describe 'utility methods' do
528
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
529
+ record1 = ScopedOrderable.where(group_id: 1, position: 1).first
530
+ record2 = ScopedOrderable.where(group_id: 1, position: 2).first
531
+ record3 = ScopedOrderable.where(group_id: 2, position: 1).first
532
+ record4 = ScopedOrderable.where(group_id: 2, position: 2).first
533
+ record5 = ScopedOrderable.where(group_id: 2, position: 3).first
534
+ expect(record1.next_items.to_a).to eq([record2])
535
+ expect(record5.previous_items.to_a).to eq([record3, record4])
536
+ expect(record3.previous_items.to_a).to eq([])
537
+ expect(record3.next_items.to_a).to eq([record4, record5])
538
+ expect(record1.next_item).to eq(record2)
539
+ expect(record2.previous_item).to eq(record1)
540
+ expect(record1.previous_item).to eq(nil)
541
+ expect(record2.next_item).to eq(nil)
542
+ end
543
+ end
544
+
545
+ describe 'concurrency' do
546
+ it 'should correctly move items to top' do
547
+ 20.times.map do
548
+ Thread.new do
549
+ record = ScopedOrderable.all.sample
550
+ record.update_attributes move_to: :top
551
+ end
552
+ end.each(&:join)
553
+
554
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
555
+ end
556
+
557
+ it 'should correctly move items to bottom' do
558
+ 20.times.map do
559
+ Thread.new do
560
+ record = ScopedOrderable.all.sample
561
+ record.update_attributes move_to: :bottom
562
+ end
563
+ end.each(&:join)
564
+
565
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
566
+ end
567
+
568
+ it 'should correctly move items higher' do
569
+ 20.times.map do
570
+ Thread.new do
571
+ record = ScopedOrderable.all.sample
572
+ record.update_attributes move_to: :higher
573
+ end
574
+ end.each(&:join)
575
+
576
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
577
+ end
578
+
579
+ it 'should correctly move items lower' do
580
+ 20.times.map do
581
+ Thread.new do
582
+ record = ScopedOrderable.all.sample
583
+ record.update_attributes move_to: :lower
584
+ end
585
+ end.each(&:join)
586
+
587
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
588
+ end
589
+
590
+ it 'should correctly move items to a random position' do
591
+ 20.times.map do
592
+ Thread.new do
593
+ record = ScopedOrderable.all.sample
594
+ record.update_attributes move_to: (1..5).to_a.sample
595
+ end
596
+ end.each(&:join)
597
+
598
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
599
+ end
600
+
601
+ # This spec fails randomly
602
+ it 'should correctly move items to a random scope', retry: 5 do
603
+ 20.times.map do
604
+ Thread.new do
605
+ record = ScopedOrderable.all.sample
606
+ group_id = ([1, 2, 3] - [record.group_id]).sample
607
+ record.update_attributes group_id: group_id
608
+ end
609
+ end.each(&:join)
610
+
611
+ result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
612
+ hash[obj.group_id] ||= []
613
+ hash[obj.group_id] << obj.position
614
+ end
615
+
616
+ result.values.each do |ary|
617
+ expect(ary.sort).to eq((1..(ary.size)).to_a)
618
+ end
619
+ end
620
+
621
+ it 'should correctly move items to a random position and scope' do
622
+ 20.times.map do
623
+ Thread.new do
624
+ record = ScopedOrderable.all.sample
625
+ group_id = ([1, 2, 3] - [record.group_id]).sample
626
+ position = (1..5).to_a.sample
627
+ record.update_attributes group_id: group_id, move_to: position
628
+ end
629
+ end.each(&:join)
630
+
631
+ result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
632
+ hash[obj.group_id] ||= []
633
+ hash[obj.group_id] << obj.position
634
+ end
635
+
636
+ result.values.each do |ary|
637
+ expect(ary.sort).to eq((1..(ary.size)).to_a)
638
+ end
639
+ end
640
+ end
641
+ end
642
+
643
+ describe StringScopedOrderable do
644
+ it 'uses the foreign key of the relationship as scope' do
645
+ orderable1 = StringScopedOrderable.create!(some_scope: 1)
646
+ orderable2 = StringScopedOrderable.create!(some_scope: 1)
647
+ orderable3 = StringScopedOrderable.create!(some_scope: 2)
648
+ expect(orderable1.position).to eq 1
649
+ expect(orderable2.position).to eq 2
650
+ expect(orderable3.position).to eq 1
651
+ end
652
+ end
653
+
654
+ describe EmbeddedOrderable do
655
+ before :each do
656
+ eo = EmbedsOrderable.create!
657
+ 2.times { eo.embedded_orderables.create! }
658
+ eo = EmbedsOrderable.create!
659
+ 3.times { eo.embedded_orderables.create! }
660
+ end
661
+
662
+ def positions
663
+ EmbedsOrderable.order_by(position: 1).all.map { |eo| eo.embedded_orderables.map(&:position).sort }
664
+ end
665
+
666
+ it 'sets proper position while creation' do
667
+ expect(positions).to eq([[1, 2], [1, 2, 3]])
668
+ end
669
+
670
+ it 'moves an item returned by a query to position' do
671
+ embedded_orderable1 = EmbedsOrderable.first.embedded_orderables.where(position: 1).first
672
+ embedded_orderable2 = EmbedsOrderable.first.embedded_orderables.where(position: 2).first
673
+ embedded_orderable1.move_to! 2
674
+ expect(embedded_orderable2.reload.position).to eq(1)
675
+ end
676
+ end
677
+
678
+ describe CustomizedOrderable do
679
+ it 'does not have default position field' do
680
+ expect(CustomizedOrderable.fields).not_to have_key('position')
681
+ end
682
+
683
+ it 'should have custom pos field' do
684
+ expect(CustomizedOrderable.fields).to have_key('pos')
685
+ end
686
+
687
+ it 'should have an alias my_position which points to pos field on Mongoid 3+' do
688
+ if CustomizedOrderable.respond_to?(:database_field_name)
689
+ expect(CustomizedOrderable.database_field_name('my_position')).to eq('pos')
690
+ end
691
+ end
692
+ end
693
+
694
+ describe NoIndexOrderable do
695
+ it 'should not have index on position field' do
696
+ expect(NoIndexOrderable.index_specifications.detect { |spec| spec.key == :position }).to be_nil
697
+ end
698
+ end
699
+
700
+ describe ZeroBasedOrderable do
701
+ before :each do
702
+ 5.times { ZeroBasedOrderable.create! }
703
+ end
704
+
705
+ def positions
706
+ ZeroBasedOrderable.pluck(:position).sort
707
+ end
708
+
709
+ it 'should have a orderable base of 0' do
710
+ expect(ZeroBasedOrderable.create!.orderable_top).to eq(0)
711
+ end
712
+
713
+ it 'should set proper position while creation' do
714
+ expect(positions).to eq([0, 1, 2, 3, 4])
715
+ end
716
+
717
+ describe 'reset position' do
718
+ before { ZeroBasedOrderable.update_all(position: nil) }
719
+ it 'should properly reset position' do
720
+ ZeroBasedOrderable.all.map(&:save)
721
+ expect(positions).to eq([0, 1, 2, 3, 4])
722
+ end
723
+ end
724
+
725
+ describe 'removement' do
726
+ it 'top' do
727
+ ZeroBasedOrderable.where(position: 0).destroy
728
+ expect(positions).to eq([0, 1, 2, 3])
729
+ end
730
+
731
+ it 'bottom' do
732
+ ZeroBasedOrderable.where(position: 4).destroy
733
+ expect(positions).to eq([0, 1, 2, 3])
734
+ end
735
+
736
+ it 'middle' do
737
+ ZeroBasedOrderable.where(position: 2).destroy
738
+ expect(positions).to eq([0, 1, 2, 3])
739
+ end
740
+ end
741
+
742
+ describe 'inserting' do
743
+ it 'top' do
744
+ newbie = ZeroBasedOrderable.create! move_to: :top
745
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
746
+ expect(newbie.position).to eq(0)
747
+ end
748
+
749
+ it 'bottom' do
750
+ newbie = ZeroBasedOrderable.create! move_to: :bottom
751
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
752
+ expect(newbie.position).to eq(5)
753
+ end
754
+
755
+ it 'middle' do
756
+ newbie = ZeroBasedOrderable.create! move_to: 3
757
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
758
+ expect(newbie.position).to eq(3)
759
+ end
760
+
761
+ it 'middle (with a numeric string)' do
762
+ newbie = ZeroBasedOrderable.create! move_to: '3'
763
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
764
+ expect(newbie.position).to eq(3)
765
+ end
766
+
767
+ it 'middle (with a non-numeric string)' do
768
+ expect do
769
+ ZeroBasedOrderable.create! move_to: 'three'
770
+ end.to raise_error Mongoid::Orderable::Errors::InvalidTargetPosition
771
+ end
772
+ end
773
+
774
+ describe 'movement' do
775
+ it 'higher from top' do
776
+ record = ZeroBasedOrderable.where(position: 0).first
777
+ record.update_attributes move_to: :higher
778
+ expect(positions).to eq([0, 1, 2, 3, 4])
779
+ expect(record.reload.position).to eq(0)
780
+ end
781
+
782
+ it 'higher from bottom' do
783
+ record = ZeroBasedOrderable.where(position: 4).first
784
+ record.update_attributes move_to: :higher
785
+ expect(positions).to eq([0, 1, 2, 3, 4])
786
+ expect(record.reload.position).to eq(3)
787
+ end
788
+
789
+ it 'higher from middle' do
790
+ record = ZeroBasedOrderable.where(position: 3).first
791
+ record.update_attributes move_to: :higher
792
+ expect(positions).to eq([0, 1, 2, 3, 4])
793
+ expect(record.reload.position).to eq(2)
794
+ end
795
+
796
+ it 'lower from top' do
797
+ record = ZeroBasedOrderable.where(position: 0).first
798
+ record.update_attributes move_to: :lower
799
+ expect(positions).to eq([0, 1, 2, 3, 4])
800
+ expect(record.reload.position).to eq(1)
801
+ end
802
+
803
+ it 'lower from bottom' do
804
+ record = ZeroBasedOrderable.where(position: 4).first
805
+ record.update_attributes move_to: :lower
806
+ expect(positions).to eq([0, 1, 2, 3, 4])
807
+ expect(record.reload.position).to eq(4)
808
+ end
809
+
810
+ it 'lower from middle' do
811
+ record = ZeroBasedOrderable.where(position: 2).first
812
+ record.update_attributes move_to: :lower
813
+ expect(positions).to eq([0, 1, 2, 3, 4])
814
+ expect(record.reload.position).to eq(3)
815
+ end
816
+
817
+ it 'does nothing if position not change' do
818
+ record = ZeroBasedOrderable.where(position: 3).first
819
+ record.save
820
+ expect(positions).to eq([0, 1, 2, 3, 4])
821
+ expect(record.reload.position).to eq(3)
822
+ end
823
+ end
824
+
825
+ describe 'utility methods' do
826
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
827
+ record1 = ZeroBasedOrderable.where(position: 0).first
828
+ record2 = ZeroBasedOrderable.where(position: 1).first
829
+ record3 = ZeroBasedOrderable.where(position: 2).first
830
+ record4 = ZeroBasedOrderable.where(position: 3).first
831
+ record5 = ZeroBasedOrderable.where(position: 4).first
832
+ expect(record1.next_items.to_a).to eq([record2, record3, record4, record5])
833
+ expect(record5.previous_items.to_a).to eq([record1, record2, record3, record4])
834
+ expect(record3.previous_items.to_a).to eq([record1, record2])
835
+ expect(record3.next_items.to_a).to eq([record4, record5])
836
+ expect(record1.next_item).to eq(record2)
837
+ expect(record2.previous_item).to eq(record1)
838
+ expect(record1.previous_item).to eq(nil)
839
+ expect(record5.next_item).to eq(nil)
840
+ end
841
+ end
842
+ end
843
+
844
+ describe Fruit do
845
+ it 'should set proper position' do
846
+ fruit1 = Apple.create
847
+ fruit2 = Orange.create
848
+ expect(fruit1.position).to eq(1)
849
+ expect(fruit2.position).to eq(2)
850
+ end
851
+
852
+ describe 'movement' do
853
+ before :each do
854
+ 5.times { Apple.create! }
855
+ end
856
+
857
+ it 'with symbol position' do
858
+ first_apple = Apple.asc(:_id).first
859
+ top_pos = first_apple.position
860
+ bottom_pos = Apple.asc(:_id).last.position
861
+ expect do
862
+ first_apple.move_to! :bottom
863
+ end.to change(first_apple, :position).from(top_pos).to bottom_pos
864
+ end
865
+
866
+ it 'with point position' do
867
+ first_apple = Apple.asc(:_id).first
868
+ top_pos = first_apple.position
869
+ bottom_pos = Apple.asc(:_id).last.position
870
+ expect do
871
+ first_apple.move_to! bottom_pos
872
+ end.to change(first_apple, :position).from(top_pos).to bottom_pos
873
+ end
874
+ end
875
+
876
+ describe 'add orderable configs in inherited class' do
877
+ it 'does not affect the orderable configs of parent class and sibling class' do
878
+ class Apple
879
+ orderable field: :serial
880
+ end
881
+ expect(Fruit.orderable_configs).not_to eq Apple.orderable_configs
882
+ expect(Orange.orderable_configs).not_to eq Apple.orderable_configs
883
+ expect(Fruit.orderable_configs).to eq Orange.orderable_configs
884
+ end
885
+ end
886
+ end
887
+
888
+ describe ForeignKeyDiffersOrderable do
889
+ it 'uses the foreign key of the relationship as scope' do
890
+ orderable1, orderable2, orderable3 = nil
891
+ parent_scope1 = ForeignKeyDiffersOrderable.create
892
+ parent_scope2 = ForeignKeyDiffersOrderable.create
893
+ expect do
894
+ orderable1 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope1)
895
+ orderable2 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope1)
896
+ orderable3 = ForeignKeyDiffersOrderable.create!(different_scope: parent_scope2)
897
+ end.to_not raise_error
898
+ expect(orderable1.position).to eq 1
899
+ expect(orderable2.position).to eq 2
900
+ expect(orderable3.position).to eq 1
901
+ end
902
+ end
903
+
904
+ describe MultipleFieldsOrderable do
905
+ before :each do
906
+ 5.times { MultipleFieldsOrderable.create! }
907
+ end
908
+
909
+ context 'default orderable' do
910
+ let(:serial_nos) { MultipleFieldsOrderable.pluck(:serial_no).sort }
911
+
912
+ describe 'inserting' do
913
+ let(:newbie) { MultipleFieldsOrderable.create! }
914
+
915
+ before { @position = newbie.position }
916
+
917
+ it 'top' do
918
+ newbie.move_to! :top
919
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
920
+ expect(newbie.serial_no).to eq(1)
921
+ expect(newbie.position).to eq(@position)
922
+ end
923
+
924
+ it 'bottom' do
925
+ newbie.move_to! :bottom
926
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
927
+ expect(newbie.serial_no).to eq(6)
928
+ expect(newbie.position).to eq(@position)
929
+ end
930
+
931
+ it 'middle' do
932
+ newbie.move_to! 4
933
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
934
+ expect(newbie.serial_no).to eq(4)
935
+ expect(newbie.position).to eq(@position)
936
+ end
937
+ end
938
+
939
+ describe 'movement' do
940
+ it 'higher from top' do
941
+ record = MultipleFieldsOrderable.where(serial_no: 1).first
942
+ position = record.position
943
+ record.move_higher!
944
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
945
+ expect(record.serial_no).to eq(1)
946
+ expect(record.position).to eq(position)
947
+ end
948
+
949
+ it 'higher from bottom' do
950
+ record = MultipleFieldsOrderable.where(serial_no: 5).first
951
+ position = record.position
952
+ record.move_higher!
953
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
954
+ expect(record.serial_no).to eq(4)
955
+ expect(record.position).to eq(position)
956
+ end
957
+
958
+ it 'higher from middle' do
959
+ record = MultipleFieldsOrderable.where(serial_no: 3).first
960
+ position = record.position
961
+ record.move_higher!
962
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
963
+ expect(record.serial_no).to eq(2)
964
+ expect(record.position).to eq(position)
965
+ end
966
+
967
+ it 'lower from top' do
968
+ record = MultipleFieldsOrderable.where(serial_no: 1).first
969
+ position = record.position
970
+ record.move_lower!
971
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
972
+ expect(record.serial_no).to eq(2)
973
+ expect(record.position).to eq(position)
974
+ end
975
+
976
+ it 'lower from bottom' do
977
+ record = MultipleFieldsOrderable.where(serial_no: 5).first
978
+ position = record.position
979
+ record.move_lower!
980
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
981
+ expect(record.serial_no).to eq(5)
982
+ expect(record.position).to eq(position)
983
+ end
984
+
985
+ it 'lower from middle' do
986
+ record = MultipleFieldsOrderable.where(serial_no: 3).first
987
+ position = record.position
988
+ record.move_lower!
989
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
990
+ expect(record.serial_no).to eq(4)
991
+ expect(record.position).to eq(position)
992
+ end
993
+ end
994
+
995
+ describe 'utility methods' do
996
+ before do
997
+ @record1 = MultipleFieldsOrderable.where(serial_no: 1).first
998
+ @record2 = MultipleFieldsOrderable.where(serial_no: 2).first
999
+ @record3 = MultipleFieldsOrderable.where(serial_no: 3).first
1000
+ @record4 = MultipleFieldsOrderable.where(serial_no: 4).first
1001
+ @record5 = MultipleFieldsOrderable.where(serial_no: 5).first
1002
+ end
1003
+
1004
+ it 'should return the lower/higher item on the list for next_item/previous_item' do
1005
+ expect(@record1.next_item).to eq(@record2)
1006
+ expect(@record3.next_item).to eq(@record4)
1007
+ expect(@record5.next_item).to eq(nil)
1008
+ expect(@record1.prev_item).to eq(nil)
1009
+ expect(@record3.prev_item).to eq(@record2)
1010
+ expect(@record5.prev_item).to eq(@record4)
1011
+ end
1012
+
1013
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1014
+ expect(@record1.next_items.to_a).to eq([@record2, @record3, @record4, @record5])
1015
+ expect(@record3.next_items.to_a).to eq([@record4, @record5])
1016
+ expect(@record5.next_items.to_a).to eq([])
1017
+ expect(@record1.previous_items.to_a).to eq([])
1018
+ expect(@record3.previous_items.to_a).to eq([@record1, @record2])
1019
+ expect(@record5.previous_items.to_a).to eq([@record1, @record2, @record3, @record4])
1020
+ end
1021
+ end
1022
+ end
1023
+
1024
+ context 'serial_no orderable' do
1025
+ let(:serial_nos) { MultipleFieldsOrderable.pluck(:serial_no).sort }
1026
+
1027
+ it 'should have proper serial_no field' do
1028
+ expect(MultipleFieldsOrderable.fields.key?('serial_no')).to be true
1029
+ expect(MultipleFieldsOrderable.fields['serial_no'].options[:type]).to eq(Integer)
1030
+ end
1031
+
1032
+ it 'should have index on serial_no field' do
1033
+ expect(MultipleFieldsOrderable.index_specifications.detect { |spec| spec.key == { serial_no: 1 } }).not_to be_nil
1034
+ end
1035
+
1036
+ it 'should have a orderable base of 1' do
1037
+ expect(MultipleFieldsOrderable.first.orderable_top(:serial_no)).to eq(1)
1038
+ end
1039
+
1040
+ it 'should set proper position while creation' do
1041
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1042
+ end
1043
+
1044
+ describe 'removement' do
1045
+ it 'top' do
1046
+ MultipleFieldsOrderable.where(serial_no: 1).destroy
1047
+ expect(serial_nos).to eq([1, 2, 3, 4])
1048
+ end
1049
+
1050
+ it 'bottom' do
1051
+ MultipleFieldsOrderable.where(serial_no: 5).destroy
1052
+ expect(serial_nos).to eq([1, 2, 3, 4])
1053
+ end
1054
+
1055
+ it 'middle' do
1056
+ MultipleFieldsOrderable.where(serial_no: 3).destroy
1057
+ expect(serial_nos).to eq([1, 2, 3, 4])
1058
+ end
1059
+ end
1060
+
1061
+ describe 'inserting' do
1062
+ let(:newbie) { MultipleFieldsOrderable.create! }
1063
+
1064
+ before { @position = newbie.position }
1065
+
1066
+ it 'top' do
1067
+ newbie.move_serial_no_to! :top
1068
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
1069
+ expect(newbie.serial_no).to eq(1)
1070
+ expect(newbie.position).to eq(@position)
1071
+ end
1072
+
1073
+ it 'bottom' do
1074
+ newbie.move_serial_no_to! :bottom
1075
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
1076
+ expect(newbie.serial_no).to eq(6)
1077
+ expect(newbie.position).to eq(@position)
1078
+ end
1079
+
1080
+ it 'middle' do
1081
+ newbie.move_serial_no_to! 4
1082
+ expect(serial_nos).to eq([1, 2, 3, 4, 5, 6])
1083
+ expect(newbie.serial_no).to eq(4)
1084
+ expect(newbie.position).to eq(@position)
1085
+ end
1086
+ end
1087
+
1088
+ describe 'movement' do
1089
+ it 'higher from top' do
1090
+ record = MultipleFieldsOrderable.where(serial_no: 1).first
1091
+ position = record.position
1092
+ record.move_serial_no_higher!
1093
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1094
+ expect(record.serial_no).to eq(1)
1095
+ expect(record.position).to eq(position)
1096
+ end
1097
+
1098
+ it 'higher from bottom' do
1099
+ record = MultipleFieldsOrderable.where(serial_no: 5).first
1100
+ position = record.position
1101
+ record.move_serial_no_higher!
1102
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1103
+ expect(record.serial_no).to eq(4)
1104
+ expect(record.position).to eq(position)
1105
+ end
1106
+
1107
+ it 'higher from middle' do
1108
+ record = MultipleFieldsOrderable.where(serial_no: 3).first
1109
+ position = record.position
1110
+ record.move_serial_no_higher!
1111
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1112
+ expect(record.serial_no).to eq(2)
1113
+ expect(record.position).to eq(position)
1114
+ end
1115
+
1116
+ it 'lower from top' do
1117
+ record = MultipleFieldsOrderable.where(serial_no: 1).first
1118
+ position = record.position
1119
+ record.move_serial_no_lower!
1120
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1121
+ expect(record.serial_no).to eq(2)
1122
+ expect(record.position).to eq(position)
1123
+ end
1124
+
1125
+ it 'lower from bottom' do
1126
+ record = MultipleFieldsOrderable.where(serial_no: 5).first
1127
+ position = record.position
1128
+ record.move_serial_no_lower!
1129
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1130
+ expect(record.serial_no).to eq(5)
1131
+ expect(record.position).to eq(position)
1132
+ end
1133
+
1134
+ it 'lower from middle' do
1135
+ record = MultipleFieldsOrderable.where(serial_no: 3).first
1136
+ position = record.position
1137
+ record.move_serial_no_lower!
1138
+ expect(serial_nos).to eq([1, 2, 3, 4, 5])
1139
+ expect(record.serial_no).to eq(4)
1140
+ expect(record.position).to eq(position)
1141
+ end
1142
+ end
1143
+
1144
+ describe 'utility methods' do
1145
+ before do
1146
+ @record1 = MultipleFieldsOrderable.where(serial_no: 1).first
1147
+ @record2 = MultipleFieldsOrderable.where(serial_no: 2).first
1148
+ @record3 = MultipleFieldsOrderable.where(serial_no: 3).first
1149
+ @record4 = MultipleFieldsOrderable.where(serial_no: 4).first
1150
+ @record5 = MultipleFieldsOrderable.where(serial_no: 5).first
1151
+ end
1152
+
1153
+ it 'should return the lower/higher item on the list for next_item/previous_item' do
1154
+ expect(@record1.next_serial_no_item).to eq(@record2)
1155
+ expect(@record3.next_serial_no_item).to eq(@record4)
1156
+ expect(@record5.next_serial_no_item).to eq(nil)
1157
+ expect(@record1.prev_serial_no_item).to eq(nil)
1158
+ expect(@record3.prev_serial_no_item).to eq(@record2)
1159
+ expect(@record5.prev_serial_no_item).to eq(@record4)
1160
+ end
1161
+
1162
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1163
+ expect(@record1.next_serial_no_items.to_a).to eq([@record2, @record3, @record4, @record5])
1164
+ expect(@record3.next_serial_no_items.to_a).to eq([@record4, @record5])
1165
+ expect(@record5.next_serial_no_items.to_a).to eq([])
1166
+ expect(@record1.previous_serial_no_items.to_a).to eq([])
1167
+ expect(@record3.previous_serial_no_items.to_a).to eq([@record1, @record2])
1168
+ expect(@record5.previous_serial_no_items.to_a).to eq([@record1, @record2, @record3, @record4])
1169
+ end
1170
+ end
1171
+ end
1172
+
1173
+ context 'position orderable' do
1174
+ let(:positions) { MultipleFieldsOrderable.pluck(:position).sort }
1175
+
1176
+ it 'should not have default position field' do
1177
+ expect(MultipleFieldsOrderable.fields).not_to have_key('position')
1178
+ end
1179
+
1180
+ it 'should have custom pos field' do
1181
+ expect(MultipleFieldsOrderable.fields).to have_key('pos')
1182
+ expect(MultipleFieldsOrderable.fields['pos'].options[:type]).to eq(Integer)
1183
+ end
1184
+
1185
+ it 'should have index on position field' do
1186
+ expect(MultipleFieldsOrderable.index_specifications.detect { |spec| spec.key == { position: 1 } }).to be_nil
1187
+ end
1188
+
1189
+ it 'should have a orderable base of 0' do
1190
+ expect(MultipleFieldsOrderable.first.orderable_top(:position)).to eq(0)
1191
+ end
1192
+
1193
+ it 'should set proper position while creation' do
1194
+ expect(positions).to eq([0, 1, 2, 3, 4])
1195
+ end
1196
+
1197
+ describe 'removement' do
1198
+ it 'top' do
1199
+ MultipleFieldsOrderable.where(pos: 1).destroy
1200
+ expect(positions).to eq([0, 1, 2, 3])
1201
+ end
1202
+
1203
+ it 'bottom' do
1204
+ MultipleFieldsOrderable.where(pos: 4).destroy
1205
+ expect(positions).to eq([0, 1, 2, 3])
1206
+ end
1207
+
1208
+ it 'middle' do
1209
+ MultipleFieldsOrderable.where(pos: 3).destroy
1210
+ expect(positions).to eq([0, 1, 2, 3])
1211
+ end
1212
+ end
1213
+
1214
+ describe 'inserting' do
1215
+ let(:newbie) { MultipleFieldsOrderable.create! }
1216
+
1217
+ before { @serial_no = newbie.serial_no }
1218
+
1219
+ it 'top' do
1220
+ newbie.move_position_to! :top
1221
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
1222
+ expect(newbie.position).to eq(0)
1223
+ expect(newbie.serial_no).to eq(@serial_no)
1224
+ end
1225
+
1226
+ it 'bottom' do
1227
+ newbie.move_position_to! :bottom
1228
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
1229
+ expect(newbie.position).to eq(5)
1230
+ expect(newbie.serial_no).to eq(@serial_no)
1231
+ end
1232
+
1233
+ it 'middle' do
1234
+ newbie.move_position_to! 4
1235
+ expect(positions).to eq([0, 1, 2, 3, 4, 5])
1236
+ expect(newbie.position).to eq(4)
1237
+ expect(newbie.serial_no).to eq(@serial_no)
1238
+ end
1239
+ end
1240
+
1241
+ describe 'movement' do
1242
+ it 'higher from top' do
1243
+ record = MultipleFieldsOrderable.where(pos: 0).first
1244
+ position = record.serial_no
1245
+ record.move_position_higher!
1246
+ expect(positions).to eq([0, 1, 2, 3, 4])
1247
+ expect(record.position).to eq(0)
1248
+ expect(record.serial_no).to eq(position)
1249
+ end
1250
+
1251
+ it 'higher from bottom' do
1252
+ record = MultipleFieldsOrderable.where(pos: 4).first
1253
+ position = record.serial_no
1254
+ record.move_position_higher!
1255
+ expect(positions).to eq([0, 1, 2, 3, 4])
1256
+ expect(record.position).to eq(3)
1257
+ expect(record.serial_no).to eq(position)
1258
+ end
1259
+
1260
+ it 'higher from middle' do
1261
+ record = MultipleFieldsOrderable.where(pos: 3).first
1262
+ position = record.serial_no
1263
+ record.move_position_higher!
1264
+ expect(positions).to eq([0, 1, 2, 3, 4])
1265
+ expect(record.position).to eq(2)
1266
+ expect(record.serial_no).to eq(position)
1267
+ end
1268
+
1269
+ it 'lower from top' do
1270
+ record = MultipleFieldsOrderable.where(pos: 0).first
1271
+ position = record.serial_no
1272
+ record.move_position_lower!
1273
+ expect(positions).to eq([0, 1, 2, 3, 4])
1274
+ expect(record.position).to eq(1)
1275
+ expect(record.serial_no).to eq(position)
1276
+ end
1277
+
1278
+ it 'lower from bottom' do
1279
+ record = MultipleFieldsOrderable.where(pos: 4).first
1280
+ position = record.serial_no
1281
+ record.move_position_lower!
1282
+ expect(positions).to eq([0, 1, 2, 3, 4])
1283
+ expect(record.position).to eq(4)
1284
+ expect(record.serial_no).to eq(position)
1285
+ end
1286
+
1287
+ it 'lower from middle' do
1288
+ record = MultipleFieldsOrderable.where(pos: 3).first
1289
+ position = record.serial_no
1290
+ record.move_position_lower!
1291
+ expect(positions).to eq([0, 1, 2, 3, 4])
1292
+ expect(record.position).to eq(4)
1293
+ expect(record.serial_no).to eq(position)
1294
+ end
1295
+ end
1296
+
1297
+ describe 'utility methods' do
1298
+ before do
1299
+ @record1 = MultipleFieldsOrderable.where(pos: 0).first
1300
+ @record2 = MultipleFieldsOrderable.where(pos: 1).first
1301
+ @record3 = MultipleFieldsOrderable.where(pos: 2).first
1302
+ @record4 = MultipleFieldsOrderable.where(pos: 3).first
1303
+ @record5 = MultipleFieldsOrderable.where(pos: 4).first
1304
+ end
1305
+
1306
+ it 'should return the lower/higher item on the list for next_item/previous_item' do
1307
+ expect(@record1.next_position_item).to eq(@record2)
1308
+ expect(@record3.next_position_item).to eq(@record4)
1309
+ expect(@record5.next_position_item).to eq(nil)
1310
+ expect(@record1.prev_position_item).to eq(nil)
1311
+ expect(@record3.prev_position_item).to eq(@record2)
1312
+ expect(@record5.prev_position_item).to eq(@record4)
1313
+ end
1314
+
1315
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1316
+ expect(@record1.next_position_items.to_a).to eq([@record2, @record3, @record4, @record5])
1317
+ expect(@record3.next_position_items.to_a).to eq([@record4, @record5])
1318
+ expect(@record5.next_position_items.to_a).to eq([])
1319
+ expect(@record1.previous_position_items.to_a).to eq([])
1320
+ expect(@record3.previous_position_items.to_a).to eq([@record1, @record2])
1321
+ expect(@record5.previous_position_items.to_a).to eq([@record1, @record2, @record3, @record4])
1322
+ end
1323
+ end
1324
+ end
1325
+
1326
+ context 'group_count orderable' do
1327
+ before :each do
1328
+ MultipleFieldsOrderable.delete_all
1329
+ 2.times { MultipleFieldsOrderable.create! group_id: 1 }
1330
+ 3.times { MultipleFieldsOrderable.create! group_id: 2 }
1331
+ end
1332
+
1333
+ let(:all_groups) { MultipleFieldsOrderable.order_by([:group_id, :asc], [:groups, :asc]).map(&:groups) }
1334
+
1335
+ it 'should set proper position while creation' do
1336
+ expect(all_groups).to eq([1, 2, 1, 2, 3])
1337
+ end
1338
+
1339
+ describe 'removement' do
1340
+ it 'top' do
1341
+ MultipleFieldsOrderable.where(groups: 1, group_id: 1).destroy
1342
+ expect(all_groups).to eq([1, 1, 2, 3])
1343
+ end
1344
+
1345
+ it 'bottom' do
1346
+ MultipleFieldsOrderable.where(groups: 3, group_id: 2).destroy
1347
+ expect(all_groups).to eq([1, 2, 1, 2])
1348
+ end
1349
+
1350
+ it 'middle' do
1351
+ MultipleFieldsOrderable.where(groups: 2, group_id: 2).destroy
1352
+ expect(all_groups).to eq([1, 2, 1, 2])
1353
+ end
1354
+ end
1355
+
1356
+ describe 'inserting' do
1357
+ it 'top' do
1358
+ newbie = MultipleFieldsOrderable.create! group_id: 1
1359
+ newbie.move_groups_to! :top
1360
+ expect(all_groups).to eq([1, 2, 3, 1, 2, 3])
1361
+ expect(newbie.groups).to eq(1)
1362
+ end
1363
+
1364
+ it 'bottom' do
1365
+ newbie = MultipleFieldsOrderable.create! group_id: 2
1366
+ newbie.move_groups_to! :bottom
1367
+ expect(all_groups).to eq([1, 2, 1, 2, 3, 4])
1368
+ expect(newbie.groups).to eq(4)
1369
+ end
1370
+
1371
+ it 'middle' do
1372
+ newbie = MultipleFieldsOrderable.create! group_id: 2
1373
+ newbie.move_groups_to! 2
1374
+ expect(all_groups).to eq([1, 2, 1, 2, 3, 4])
1375
+ expect(newbie.groups).to eq(2)
1376
+ end
1377
+ end
1378
+
1379
+ describe 'scope movement' do
1380
+ let(:record) { MultipleFieldsOrderable.where(group_id: 2, groups: 2).first }
1381
+
1382
+ it 'to a new scope group' do
1383
+ record.update_attributes group_id: 3
1384
+ expect(all_groups).to eq([1, 2, 1, 2, 1])
1385
+ expect(record.groups).to eq(1)
1386
+ end
1387
+
1388
+ context 'when moving to an existing scope group' do
1389
+ it 'without a position' do
1390
+ record.update_attributes group_id: 1
1391
+ expect(all_groups).to eq([1, 2, 3, 1, 2])
1392
+ expect(record.reload.groups).to eq(3)
1393
+ end
1394
+
1395
+ it 'with symbol position' do
1396
+ record.update_attributes group_id: 1
1397
+ record.move_groups_to! :top
1398
+ expect(all_groups).to eq([1, 2, 3, 1, 2])
1399
+ expect(record.reload.groups).to eq(1)
1400
+ end
1401
+
1402
+ it 'with point position' do
1403
+ record.update_attributes group_id: 1
1404
+ record.move_groups_to! 2
1405
+ expect(all_groups).to eq([1, 2, 3, 1, 2])
1406
+ expect(record.reload.groups).to eq(2)
1407
+ end
1408
+ end
1409
+ end
1410
+
1411
+ describe 'utility methods' do
1412
+ before do
1413
+ @record1 = MultipleFieldsOrderable.where(group_id: 2, groups: 1).first
1414
+ @record2 = MultipleFieldsOrderable.where(group_id: 2, groups: 2).first
1415
+ @record3 = MultipleFieldsOrderable.where(group_id: 2, groups: 3).first
1416
+ @record4 = MultipleFieldsOrderable.where(group_id: 1, groups: 1).first
1417
+ @record5 = MultipleFieldsOrderable.where(group_id: 1, groups: 2).first
1418
+ end
1419
+
1420
+ it 'should return the lower/higher item on the list for next_item/previous_item' do
1421
+ expect(@record1.next_groups_item).to eq(@record2)
1422
+ expect(@record4.next_groups_item).to eq(@record5)
1423
+ expect(@record3.next_groups_item).to eq(nil)
1424
+ expect(@record1.prev_groups_item).to eq(nil)
1425
+ expect(@record3.prev_groups_item).to eq(@record2)
1426
+ expect(@record5.prev_groups_item).to eq(@record4)
1427
+ end
1428
+
1429
+ it 'should return a collection of items lower/higher on the list for next_items/previous_items' do
1430
+ expect(@record1.next_groups_items.to_a).to eq([@record2, @record3])
1431
+ expect(@record3.next_groups_items.to_a).to eq([])
1432
+ expect(@record4.next_groups_items.to_a).to eq([@record5])
1433
+ expect(@record1.previous_groups_items.to_a).to eq([])
1434
+ expect(@record3.previous_groups_items.to_a).to eq([@record1, @record2])
1435
+ expect(@record5.previous_groups_items.to_a).to eq([@record4])
1436
+ end
1437
+ end
1438
+ end
1439
+ end
1440
+
1441
+ describe MultipleScopedOrderable do
1442
+ before :each do
1443
+ 3.times do
1444
+ Apple.create
1445
+ Orange.create
1446
+ end
1447
+ MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
1448
+ MultipleScopedOrderable.create! apple_id: 2, orange_id: 1
1449
+ MultipleScopedOrderable.create! apple_id: 2, orange_id: 2
1450
+ MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
1451
+ MultipleScopedOrderable.create! apple_id: 1, orange_id: 1
1452
+ MultipleScopedOrderable.create! apple_id: 3, orange_id: 3
1453
+ MultipleScopedOrderable.create! apple_id: 2, orange_id: 3
1454
+ MultipleScopedOrderable.create! apple_id: 3, orange_id: 2
1455
+ MultipleScopedOrderable.create! apple_id: 1, orange_id: 3
1456
+ end
1457
+
1458
+ def apple_positions
1459
+ MultipleScopedOrderable.order_by([:apple_id, :asc], [:posa, :asc]).map(&:posa)
1460
+ end
1461
+
1462
+ def orange_positions
1463
+ MultipleScopedOrderable.order_by([:orange_id, :asc], [:poso, :asc]).map(&:poso)
1464
+ end
1465
+
1466
+ describe 'default positions' do
1467
+ it { expect(apple_positions).to eq([1, 2, 3, 4, 1, 2, 3, 1, 2]) }
1468
+ it { expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4]) }
1469
+ end
1470
+
1471
+ describe 'change the scope of the apple' do
1472
+ let(:record) { MultipleScopedOrderable.first }
1473
+ before do
1474
+ record.update_attribute(:apple_id, 2)
1475
+ end
1476
+
1477
+ it 'should properly set the apple positions' do
1478
+ expect(apple_positions).to eq([1, 2, 3, 1, 2, 3, 4, 1, 2])
1479
+ end
1480
+
1481
+ it 'should not affect the orange positions' do
1482
+ expect(orange_positions).to eq([1, 2, 3, 1, 2, 1, 2, 3, 4])
1483
+ end
1484
+ end
1485
+ end
1486
+ end