mongoid_orderable 5.0.0 → 6.0.2

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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +83 -22
  3. data/LICENSE.txt +20 -0
  4. data/README.md +256 -149
  5. data/Rakefile +24 -6
  6. data/lib/config/locales/en.yml +12 -9
  7. data/lib/mongoid/orderable.rb +29 -22
  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/errors/invalid_target_position.rb +19 -18
  11. data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -0
  12. data/lib/mongoid/orderable/generators/base.rb +21 -0
  13. data/lib/mongoid/orderable/generators/helpers.rb +29 -0
  14. data/lib/mongoid/orderable/generators/listable.rb +41 -0
  15. data/lib/mongoid/orderable/generators/lock_collection.rb +37 -0
  16. data/lib/mongoid/orderable/generators/movable.rb +62 -0
  17. data/lib/mongoid/orderable/generators/position.rb +26 -0
  18. data/lib/mongoid/orderable/generators/scope.rb +26 -0
  19. data/lib/mongoid/orderable/handlers/base.rb +167 -0
  20. data/lib/mongoid/orderable/handlers/document.rb +24 -0
  21. data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -0
  22. data/lib/mongoid/orderable/handlers/document_transactional.rb +35 -0
  23. data/lib/mongoid/orderable/handlers/transaction.rb +71 -0
  24. data/lib/mongoid/orderable/installer.rb +63 -0
  25. data/lib/mongoid/orderable/mixins/callbacks.rb +43 -0
  26. data/lib/mongoid/orderable/mixins/helpers.rb +39 -0
  27. data/lib/mongoid/orderable/mixins/listable.rb +49 -0
  28. data/lib/mongoid/orderable/mixins/movable.rb +60 -0
  29. data/lib/mongoid/orderable/version.rb +7 -0
  30. data/lib/mongoid_orderable.rb +33 -54
  31. data/spec/integration/concurrency_spec.rb +232 -0
  32. data/spec/integration/customized_spec.rb +31 -0
  33. data/spec/integration/embedded_spec.rb +41 -0
  34. data/spec/integration/foreign_key_spec.rb +33 -0
  35. data/spec/integration/inherited_spec.rb +54 -0
  36. data/spec/integration/multiple_fields_spec.rb +554 -0
  37. data/spec/integration/multiple_scoped_spec.rb +63 -0
  38. data/spec/integration/no_indexed_spec.rb +23 -0
  39. data/spec/integration/scoped_spec.rb +151 -0
  40. data/spec/integration/simple_spec.rb +184 -0
  41. data/spec/integration/string_scoped_spec.rb +28 -0
  42. data/spec/integration/zero_based_spec.rb +161 -0
  43. data/spec/spec_helper.rb +42 -30
  44. data/spec/support/models.rb +122 -0
  45. metadata +75 -41
  46. data/.gitignore +0 -4
  47. data/.rspec +0 -2
  48. data/.rvmrc +0 -1
  49. data/.travis.yml +0 -14
  50. data/Gemfile +0 -18
  51. data/lib/mongoid/orderable/callbacks.rb +0 -75
  52. data/lib/mongoid/orderable/configuration.rb +0 -60
  53. data/lib/mongoid/orderable/errors.rb +0 -2
  54. data/lib/mongoid/orderable/errors/mongoid_orderable_error.rb +0 -14
  55. data/lib/mongoid/orderable/generator.rb +0 -34
  56. data/lib/mongoid/orderable/generator/helpers.rb +0 -29
  57. data/lib/mongoid/orderable/generator/listable.rb +0 -41
  58. data/lib/mongoid/orderable/generator/movable.rb +0 -62
  59. data/lib/mongoid/orderable/generator/position.rb +0 -26
  60. data/lib/mongoid/orderable/generator/scope.rb +0 -17
  61. data/lib/mongoid/orderable/helpers.rb +0 -50
  62. data/lib/mongoid/orderable/listable.rb +0 -49
  63. data/lib/mongoid/orderable/movable.rb +0 -58
  64. data/lib/mongoid/orderable/orderable_class.rb +0 -51
  65. data/lib/mongoid_orderable/mongoid/contexts/enumerable.rb +0 -15
  66. data/lib/mongoid_orderable/mongoid/contexts/mongo.rb +0 -18
  67. data/lib/mongoid_orderable/mongoid/contextual/memory.rb +0 -15
  68. data/lib/mongoid_orderable/mongoid/criteria.rb +0 -4
  69. data/lib/mongoid_orderable/version.rb +0 -3
  70. data/mongoid_orderable.gemspec +0 -26
  71. data/spec/mongoid/orderable_spec.rb +0 -1413
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Orderable
5
+ module Mixins
6
+ module Helpers
7
+ def orderable_keys
8
+ Array(orderable_inherited_class.orderable_configs.try(:keys))
9
+ end
10
+
11
+ def default_orderable_field
12
+ self.class.orderable_configs.detect {|_c, conf| conf[:default] }.try(:first) || orderable_keys.first
13
+ end
14
+
15
+ def orderable_scope(field = nil)
16
+ field ||= default_orderable_field
17
+
18
+ if embedded?
19
+ _parent.send(_association.name).send("orderable_#{field}_scope", self)
20
+ else
21
+ orderable_inherited_class.send("orderable_#{field}_scope", self)
22
+ end
23
+ end
24
+
25
+ def orderable_scope_changed?(field)
26
+ !orderable_scope(field).where(_id: _id).exists?
27
+ end
28
+
29
+ def orderable_bottom(field = nil, in_list = true)
30
+ field ||= default_orderable_field
31
+ f = orderable_field(field)
32
+ max = orderable_scope(field).ne(f => nil).max(f)
33
+ return orderable_top(field) unless max
34
+ in_list ? max : max.next
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Orderable
5
+ module Mixins
6
+ module Listable
7
+ def in_list?(field = nil)
8
+ persisted? && !orderable_position(field).nil?
9
+ end
10
+
11
+ # Returns items above the current document.
12
+ # Items with a position lower than this document's position.
13
+ def previous_items(field = nil)
14
+ field ||= default_orderable_field
15
+ orderable_scope(field).lt(orderable_field(field) => send(field))
16
+ end
17
+ alias prev_items previous_items
18
+
19
+ # Returns items below the current document.
20
+ # Items with a position greater than this document's position.
21
+ def next_items(field = nil)
22
+ field ||= default_orderable_field
23
+ orderable_scope(field).gt(orderable_field(field) => send(field))
24
+ end
25
+
26
+ # Returns the previous item in the list
27
+ def previous_item(field = nil)
28
+ field ||= default_orderable_field
29
+ orderable_scope(field).where(orderable_field(field) => send(field) - 1).first
30
+ end
31
+ alias prev_item previous_item
32
+
33
+ # Returns the next item in the list
34
+ def next_item(field = nil)
35
+ field ||= default_orderable_field
36
+ orderable_scope(field).where(orderable_field(field) => send(field) + 1).first
37
+ end
38
+
39
+ def first?(field = nil)
40
+ in_list?(field) && orderable_position(field) == orderable_top(field)
41
+ end
42
+
43
+ def last?(field = nil)
44
+ in_list?(field) && orderable_position(field) == orderable_bottom(field)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -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.2'
6
+ end
7
+ end
@@ -1,54 +1,33 @@
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.mongoid2? || ::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
- else
19
- def self.inc instance, attribute, value
20
- instance.inc(attribute => value)
21
- end
22
-
23
- def self.metadata instance
24
- instance.relation_metadata
25
- end
26
- end
27
- end
28
-
29
- require 'mongoid_orderable/version'
30
-
31
- if ::Mongoid::Compatibility::Version.mongoid2?
32
- require 'mongoid_orderable/mongoid/contexts/mongo'
33
- require 'mongoid_orderable/mongoid/contexts/enumerable'
34
- require 'mongoid_orderable/mongoid/criteria'
35
- else
36
- require 'mongoid_orderable/mongoid/contextual/memory'
37
- end
38
-
39
- require 'mongoid/orderable'
40
- require 'mongoid/orderable/errors'
41
- require 'mongoid/orderable/configuration'
42
- require 'mongoid/orderable/helpers'
43
- require 'mongoid/orderable/callbacks'
44
- require 'mongoid/orderable/listable'
45
- require 'mongoid/orderable/movable'
46
-
47
- require 'mongoid/orderable/generator/listable'
48
- require 'mongoid/orderable/generator/movable'
49
- require 'mongoid/orderable/generator/position'
50
- require 'mongoid/orderable/generator/scope'
51
- require 'mongoid/orderable/generator/helpers'
52
- require 'mongoid/orderable/generator'
53
-
54
- 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/handlers/base'
29
+ require 'mongoid/orderable/handlers/document'
30
+ require 'mongoid/orderable/handlers/document_embedded'
31
+ require 'mongoid/orderable/handlers/document_transactional'
32
+ require 'mongoid/orderable/handlers/transaction'
33
+ require 'mongoid/orderable/installer'
@@ -0,0 +1,232 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'concurrency' do
4
+ enable_transactions!
5
+
6
+ describe 'simple create' do
7
+
8
+ it 'should correctly insert at the top' do
9
+ 20.times.map do
10
+ Thread.new do
11
+ SimpleOrderable.create!(move_to: :top)
12
+ end
13
+ end.each(&:join)
14
+
15
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
16
+ end
17
+
18
+ it 'should correctly insert at the bottom' do
19
+ 20.times.map do
20
+ Thread.new do
21
+ SimpleOrderable.create!
22
+ end
23
+ end.each(&:join)
24
+
25
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
26
+ end
27
+
28
+ it 'should correctly insert at a random position' do
29
+ 20.times.map do
30
+ Thread.new do
31
+ SimpleOrderable.create!(move_to: (1..10).to_a.sample)
32
+ end
33
+ end.each(&:join)
34
+
35
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..20).to_a)
36
+ end
37
+ end
38
+
39
+ describe 'simple update' do
40
+ before :each do
41
+ 5.times { SimpleOrderable.create! }
42
+ end
43
+
44
+ it 'should correctly move items to top' do
45
+ 20.times.map do
46
+ Thread.new do
47
+ record = SimpleOrderable.all.sample
48
+ record.update_attributes move_to: :top
49
+ end
50
+ end.each(&:join)
51
+
52
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
53
+ end
54
+
55
+ it 'should correctly move items to bottom' do
56
+ 20.times.map do
57
+ Thread.new do
58
+ record = SimpleOrderable.all.sample
59
+ record.update_attributes move_to: :bottom
60
+ end
61
+ end.each(&:join)
62
+
63
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
64
+ end
65
+
66
+ it 'should correctly move items higher' do
67
+ 20.times.map do
68
+ Thread.new do
69
+ record = SimpleOrderable.all.sample
70
+ record.update_attributes move_to: :higher
71
+ end
72
+ end.each(&:join)
73
+
74
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
75
+ end
76
+
77
+ it 'should correctly move items lower' do
78
+ 20.times.map do
79
+ Thread.new do
80
+ record = SimpleOrderable.all.sample
81
+ record.update_attributes move_to: :lower
82
+ end
83
+ end.each(&:join)
84
+
85
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
86
+ end
87
+
88
+ it 'should correctly insert at the top' do
89
+ 20.times.map do
90
+ Thread.new do
91
+ SimpleOrderable.create!(move_to: :top)
92
+ end
93
+ end.each(&:join)
94
+
95
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
96
+ end
97
+
98
+ it 'should correctly insert at the bottom' do
99
+ 20.times.map do
100
+ Thread.new do
101
+ SimpleOrderable.create!
102
+ end
103
+ end.each(&:join)
104
+
105
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
106
+ end
107
+
108
+ it 'should correctly insert at a random position' do
109
+ 20.times.map do
110
+ Thread.new do
111
+ SimpleOrderable.create!(move_to: (1..10).to_a.sample)
112
+ end
113
+ end.each(&:join)
114
+
115
+ expect(SimpleOrderable.pluck(:position).sort).to eq((1..25).to_a)
116
+ end
117
+
118
+ it 'should correctly move items to a random position' do
119
+ 20.times.map do
120
+ Thread.new do
121
+ record = SimpleOrderable.all.sample
122
+ record.update_attributes move_to: (1..5).to_a.sample
123
+ end
124
+ end.each(&:join)
125
+
126
+ expect(SimpleOrderable.pluck(:position).sort).to eq([1, 2, 3, 4, 5])
127
+ end
128
+ end
129
+
130
+ describe 'scoped update' do
131
+
132
+ before :each do
133
+ 2.times { ScopedOrderable.create! group_id: 1 }
134
+ 3.times { ScopedOrderable.create! group_id: 2 }
135
+ end
136
+
137
+ it 'should correctly move items to top' do
138
+ 20.times.map do
139
+ Thread.new do
140
+ record = ScopedOrderable.all.sample
141
+ record.update_attributes move_to: :top
142
+ end
143
+ end.each(&:join)
144
+
145
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
146
+ end
147
+
148
+ it 'should correctly move items to bottom' do
149
+ 20.times.map do
150
+ Thread.new do
151
+ record = ScopedOrderable.all.sample
152
+ record.update_attributes move_to: :bottom
153
+ end
154
+ end.each(&:join)
155
+
156
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
157
+ end
158
+
159
+ it 'should correctly move items higher' do
160
+ 20.times.map do
161
+ Thread.new do
162
+ record = ScopedOrderable.all.sample
163
+ record.update_attributes move_to: :higher
164
+ end
165
+ end.each(&:join)
166
+
167
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
168
+ end
169
+
170
+ it 'should correctly move items lower' do
171
+ 20.times.map do
172
+ Thread.new do
173
+ record = ScopedOrderable.all.sample
174
+ record.update_attributes move_to: :lower
175
+ end
176
+ end.each(&:join)
177
+
178
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
179
+ end
180
+
181
+ it 'should correctly move items to a random position' do
182
+ 20.times.map do
183
+ Thread.new do
184
+ record = ScopedOrderable.all.sample
185
+ record.update_attributes move_to: (1..5).to_a.sample
186
+ end
187
+ end.each(&:join)
188
+
189
+ expect(ScopedOrderable.pluck(:position).sort).to eq([1, 1, 2, 2, 3])
190
+ end
191
+
192
+ # This spec fails randomly
193
+ it 'should correctly move items to a random scope', retry: 5 do
194
+ 20.times.map do
195
+ Thread.new do
196
+ record = ScopedOrderable.all.sample
197
+ group_id = ([1, 2, 3] - [record.group_id]).sample
198
+ record.update_attributes group_id: group_id
199
+ end
200
+ end.each(&:join)
201
+
202
+ result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
203
+ hash[obj.group_id] ||= []
204
+ hash[obj.group_id] << obj.position
205
+ end
206
+
207
+ result.values.each do |ary|
208
+ expect(ary.sort).to eq((1..(ary.size)).to_a)
209
+ end
210
+ end
211
+
212
+ it 'should correctly move items to a random position and scope' do
213
+ 20.times.map do
214
+ Thread.new do
215
+ record = ScopedOrderable.all.sample
216
+ group_id = ([1, 2, 3] - [record.group_id]).sample
217
+ position = (1..5).to_a.sample
218
+ record.update_attributes group_id: group_id, move_to: position
219
+ end
220
+ end.each(&:join)
221
+
222
+ result = ScopedOrderable.all.to_a.each_with_object({}) do |obj, hash|
223
+ hash[obj.group_id] ||= []
224
+ hash[obj.group_id] << obj.position
225
+ end
226
+
227
+ result.values.each do |ary|
228
+ expect(ary.sort).to eq((1..(ary.size)).to_a)
229
+ end
230
+ end
231
+ end
232
+ end