mongoid_orderable 5.0.0 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
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