mongoid_orderable 5.2.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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