mongoid_orderable 6.0.4 → 6.0.6
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +101 -93
- data/LICENSE.txt +20 -20
- data/README.md +280 -275
- data/Rakefile +24 -24
- data/lib/config/locales/en.yml +12 -12
- data/lib/mongoid/orderable/configs/field_config.rb +81 -81
- data/lib/mongoid/orderable/configs/global_config.rb +26 -26
- data/lib/mongoid/orderable/errors/invalid_target_position.rb +19 -19
- data/lib/mongoid/orderable/errors/transaction_failed.rb +20 -20
- data/lib/mongoid/orderable/generators/base.rb +21 -21
- data/lib/mongoid/orderable/generators/helpers.rb +37 -37
- data/lib/mongoid/orderable/generators/listable.rb +41 -41
- data/lib/mongoid/orderable/generators/lock_collection.rb +39 -39
- data/lib/mongoid/orderable/generators/movable.rb +62 -62
- data/lib/mongoid/orderable/generators/position.rb +26 -26
- data/lib/mongoid/orderable/generators/scope.rb +26 -26
- data/lib/mongoid/orderable/handlers/base.rb +215 -189
- data/lib/mongoid/orderable/handlers/document.rb +32 -24
- data/lib/mongoid/orderable/handlers/document_embedded.rb +14 -14
- data/lib/mongoid/orderable/handlers/document_transactional.rb +32 -35
- data/lib/mongoid/orderable/handlers/transaction.rb +75 -71
- data/lib/mongoid/orderable/installer.rb +64 -64
- data/lib/mongoid/orderable/mixins/callbacks.rb +45 -43
- data/lib/mongoid/orderable/mixins/cascadeable.rb +15 -15
- data/lib/mongoid/orderable/mixins/helpers.rb +39 -39
- data/lib/mongoid/orderable/mixins/listable.rb +49 -49
- data/lib/mongoid/orderable/mixins/movable.rb +64 -60
- data/lib/mongoid/orderable/version.rb +7 -7
- data/lib/mongoid/orderable.rb +29 -29
- data/lib/mongoid_orderable.rb +34 -34
- metadata +6 -93
- data/spec/integration/cascadeable_spec.rb +0 -12
- data/spec/integration/concurrency_spec.rb +0 -232
- data/spec/integration/conditional_spec.rb +0 -36
- data/spec/integration/customized_spec.rb +0 -31
- data/spec/integration/embedded_spec.rb +0 -63
- data/spec/integration/foreign_key_spec.rb +0 -33
- data/spec/integration/inherited_spec.rb +0 -54
- data/spec/integration/multiple_fields_spec.rb +0 -554
- data/spec/integration/multiple_scoped_spec.rb +0 -63
- data/spec/integration/no_indexed_spec.rb +0 -23
- data/spec/integration/scoped_spec.rb +0 -151
- data/spec/integration/simple_spec.rb +0 -184
- data/spec/integration/string_scoped_spec.rb +0 -28
- data/spec/integration/zero_based_spec.rb +0 -161
- data/spec/spec_helper.rb +0 -42
- data/spec/support/models.rb +0 -134
data/README.md
CHANGED
|
@@ -1,275 +1,280 @@
|
|
|
1
|
-
[](https://badge.fury.io/rb/mongoid_orderable)
|
|
2
|
-
[](https://badge.fury.io/rb/mongoid_orderable)
|
|
2
|
+
[](https://github.com/mongoid/mongoid_orderable/actions/workflows/test.yml?query=branch%3Amaster)
|
|
3
|
+
[](https://coveralls.io/github/mongoid/mongoid_orderable?branch=master)
|
|
4
|
+
|
|
5
|
+
# Mongoid Orderable
|
|
6
|
+
|
|
7
|
+
The Ruby 4.0 CI job measures all library files with SimpleCov and submits `coverage/.resultset.json` through the official Coveralls action using GitHub's automatic token. Other matrix jobs do not upload duplicate reports. Run `COVERAGE=true bundle install` and `COVERAGE=true bundle exec rake spec` to measure coverage locally. An accepted upstream default-branch build is required for the badge; this workflow alone does not activate the external service.
|
|
8
|
+
|
|
9
|
+
Mongoid::Orderable is a ordered list implementation for your Mongoid 7+ projects.
|
|
10
|
+
|
|
11
|
+
### Core Features
|
|
12
|
+
|
|
13
|
+
* Sets a position index field on your documents which allows you to sort them in order.
|
|
14
|
+
* Uses MongoDB's `$inc` operator to batch-update position.
|
|
15
|
+
* Supports scope for position index, including changing scopes.
|
|
16
|
+
* Supports multiple position indexes on the same document.
|
|
17
|
+
* (Optional) Uses [MongoDB transactions](https://docs.mongodb.com/manual/core/transactions/) to ensure order integrity during concurrent updates.
|
|
18
|
+
|
|
19
|
+
### Version Support
|
|
20
|
+
|
|
21
|
+
As of version 6.0.0, Mongoid::Orderable supports the following dependency versions:
|
|
22
|
+
|
|
23
|
+
* Ruby 2.6+
|
|
24
|
+
* Mongoid 7.0+
|
|
25
|
+
* Rails 5.2+
|
|
26
|
+
|
|
27
|
+
For older versions, please use Mongoid::Orderable 5.x and earlier.
|
|
28
|
+
|
|
29
|
+
Transaction support requires MongoDB 4.2+ (4.4+ recommended.)
|
|
30
|
+
|
|
31
|
+
CI tests representative compatible combinations through Ruby 4.0, Mongoid 9, and MongoDB Server 8.0, retaining Ruby 2.6 / Mongoid 7 / MongoDB 4.4 coverage. See [the test matrix](.github/workflows/test.yml); this is not a Cartesian product of supported versions. Every CI test job uses an isolated single-node replica set, so transaction and non-transaction tests both run. Set `MONGOID_VERSION` to select a Mongoid major version and `MONGODB_HOST` to override the default `localhost:27017` when running tests.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Getting Started
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
gem 'mongoid_orderable'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Include `Mongoid::Orderable` into your model and call `orderable` method.
|
|
42
|
+
Embedded objects are automatically scoped to their parent.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
class MyModel
|
|
46
|
+
include Mongoid::Document
|
|
47
|
+
include Mongoid::Orderable
|
|
48
|
+
|
|
49
|
+
belongs_to :group
|
|
50
|
+
belongs_to :drawer, class_name: "Office::Drawer",
|
|
51
|
+
foreign_key: "legacy_drawer_key_id"
|
|
52
|
+
|
|
53
|
+
orderable
|
|
54
|
+
|
|
55
|
+
# if you set :scope as a symbol, it will resolve the foreign key from relation
|
|
56
|
+
orderable scope: :drawer, field: :pos
|
|
57
|
+
|
|
58
|
+
# if you set :scope as a string, it will use it as the field name for scope
|
|
59
|
+
orderable scope: 'drawer', field: :pos
|
|
60
|
+
|
|
61
|
+
# scope can also be a proc
|
|
62
|
+
orderable scope: ->(doc) { where(group_id: doc.group_id) }
|
|
63
|
+
|
|
64
|
+
# this one if you want specify indexes manually
|
|
65
|
+
orderable index: false
|
|
66
|
+
|
|
67
|
+
# count position from zero as the top-most value (1 is the default value)
|
|
68
|
+
orderable base: 0
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can also set default config values in an initializer, which will be
|
|
73
|
+
applied when calling the `orderable` macro in a model.
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
# configs/initializers/mongoid_orderable.rb
|
|
77
|
+
Mongoid::Orderable.configure do |config|
|
|
78
|
+
config.field = :pos
|
|
79
|
+
config.base = 0
|
|
80
|
+
config.index = false
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Moving Position
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
item.move_to 2 # just change position
|
|
88
|
+
item.move_to! 2 # and save
|
|
89
|
+
item.move_to = 2 # assignable method
|
|
90
|
+
|
|
91
|
+
# symbol position
|
|
92
|
+
item.move_to :top
|
|
93
|
+
item.move_to :bottom
|
|
94
|
+
item.move_to :higher
|
|
95
|
+
item.move_to :lower
|
|
96
|
+
|
|
97
|
+
# generated methods
|
|
98
|
+
item.move_to_top
|
|
99
|
+
item.move_to_bottom
|
|
100
|
+
item.move_higher
|
|
101
|
+
item.move_lower
|
|
102
|
+
|
|
103
|
+
item.next_items # return a collection of items higher on the list
|
|
104
|
+
item.previous_items # return a collection of items lower on the list
|
|
105
|
+
|
|
106
|
+
item.next_item # returns the next item in the list
|
|
107
|
+
item.previous_item # returns the previous item in the list
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Multiple Fields
|
|
111
|
+
|
|
112
|
+
You can also define multiple orderable fields for any class including the Mongoid::Orderable module.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
class Book
|
|
116
|
+
include Mongoid::Document
|
|
117
|
+
include Mongoid::Orderable
|
|
118
|
+
|
|
119
|
+
orderable base: 0
|
|
120
|
+
orderable field: sno, as: :serial_no
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The above defines two different orderable_fields on Book - *position* and *serial_no*.
|
|
125
|
+
The following helpers are generated in this case:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
book.move_#{field}_to
|
|
129
|
+
book.move_#{field}_to=
|
|
130
|
+
book.move_#{field}_to!
|
|
131
|
+
|
|
132
|
+
book.move_#{field}_to_top
|
|
133
|
+
book.move_#{field}_to_bottom
|
|
134
|
+
book.move_#{field}_higher
|
|
135
|
+
book.move_#{field}_lower
|
|
136
|
+
|
|
137
|
+
book.next_#{field}_items
|
|
138
|
+
book.previous_#{field}_items
|
|
139
|
+
|
|
140
|
+
book.next_#{field}_item
|
|
141
|
+
book.previous_#{field}_item
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
where `#{field}` is either `position` or `serial_no`.
|
|
145
|
+
|
|
146
|
+
When a model defines multiple orderable fields, the original helpers are also available and work
|
|
147
|
+
on the first orderable field.
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
@book1 = Book.create!
|
|
151
|
+
@book2 = Book.create!
|
|
152
|
+
@book2 # => <Book _id: 53a16a2ba1bde4f746000001, serial_no: 1, position: 1>
|
|
153
|
+
@book2.move_to! :top # this will change the :position of the book to 0 (not serial_no)
|
|
154
|
+
@book2 # => <Book _id: 53a16a2ba1bde4f746000001, serial_no: 1, position: 0>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
To specify any other orderable field as default pass the **default: true** option with orderable.
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
orderable field: sno, as: :serial_no, default: true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Embedded Documents
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
class Question
|
|
167
|
+
include Mongoid::Document
|
|
168
|
+
include Mongoid::Orderable
|
|
169
|
+
|
|
170
|
+
embedded_in :survey
|
|
171
|
+
|
|
172
|
+
orderable
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
If you bulk import embedded documents without specifying their position,
|
|
177
|
+
no field `position` will be written.
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
class Survey
|
|
181
|
+
include Mongoid::Document
|
|
182
|
+
|
|
183
|
+
embeds_many :questions, cascade_callbacks: true
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
To ensure the position is written correctly, you will need to set
|
|
188
|
+
`cascade_callbacks: true` on the relation.
|
|
189
|
+
|
|
190
|
+
### Disable Ordering
|
|
191
|
+
|
|
192
|
+
You can disable position tracking for specific documents using the `:if` and `:unless` options.
|
|
193
|
+
This is in advanced scenarios where you want to control position manually for certain documents.
|
|
194
|
+
In general, the disable condition should match a specific scope.
|
|
195
|
+
**Warning:** If used improperly, this will cause your documents to become out-of-order.
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
class Book
|
|
199
|
+
include Mongoid::Document
|
|
200
|
+
include Mongoid::Orderable
|
|
201
|
+
|
|
202
|
+
field :track_position, type: Boolean
|
|
203
|
+
|
|
204
|
+
orderable if: :track_position, unless: -> { created_at < Date.parse('2020-01-01') }
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Transaction Support
|
|
209
|
+
|
|
210
|
+
By default, Mongoid Orderable does not guarantee ordering consistency
|
|
211
|
+
when doing multiple concurrent updates on documents. This means that
|
|
212
|
+
instead of having positions `1, 2, 3, 4, 5`, after running your system
|
|
213
|
+
in production at scale your position data will become corrupted, e.g.
|
|
214
|
+
`1, 1, 4, 4, 6`. To remedy this, this Mongoid Orderable can use
|
|
215
|
+
[MongoDB transactions](https://docs.mongodb.com/manual/core/transactions/)
|
|
216
|
+
|
|
217
|
+
### Prerequisites
|
|
218
|
+
|
|
219
|
+
* MongoDB version 4.2+ (4.4+ recommended.)
|
|
220
|
+
* Requires [MongoDB Replica Set](https://docs.mongodb.com/manual/tutorial/deploy-replica-set/) topology
|
|
221
|
+
|
|
222
|
+
### Configuration
|
|
223
|
+
|
|
224
|
+
You may enable transactions on both the global and model configs:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
Mongoid::Orderable.configure do |config|
|
|
228
|
+
config.use_transactions = true # default: false
|
|
229
|
+
config.transaction_max_retries = 10 # default: 10
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
class MyModel
|
|
233
|
+
orderable :position, use_transactions: false
|
|
234
|
+
end
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
When two transactions are attempted at the same time, database-level
|
|
238
|
+
`WriteConflict` failures may result and retries will be attempted.
|
|
239
|
+
After `transaction_max_retries` has been exceeded, a
|
|
240
|
+
`Mongoid::Orderable::Errors::TransactionFailed` error will be raised.
|
|
241
|
+
|
|
242
|
+
### Locks
|
|
243
|
+
|
|
244
|
+
When using transactions, Mongoid Orderable creates a collection
|
|
245
|
+
`mongoid_orderable_locks` which is used to store temporary lock objects.
|
|
246
|
+
Lock collections use a TTL index which auto-deletes objects older than 1 day.
|
|
247
|
+
|
|
248
|
+
You can change the lock collection name globally or per model:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
Mongoid::Orderable.configure do |config|
|
|
252
|
+
config.lock_collection = "my_locks" # default: "mongoid_orderable_locks"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
class MyModel
|
|
256
|
+
orderable :position, lock_collection: "my_model_locks"
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### MongoDB 4.2 Support
|
|
261
|
+
|
|
262
|
+
In MongoDB 4.2, collections cannot be created within transactions.
|
|
263
|
+
Therefore, you will need to manually run the following command once
|
|
264
|
+
to initialize the lock collection:
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
Mongoid::Orderable::Models::Lock.create!
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This step is not necessary when using MongoDB 4.4+.
|
|
271
|
+
|
|
272
|
+
### Contributing
|
|
273
|
+
|
|
274
|
+
Please fork the project on Github and raise a pull request including passing specs.
|
|
275
|
+
|
|
276
|
+
### Copyright & License
|
|
277
|
+
|
|
278
|
+
Copyright (c) 2011 Arkadiy Zabazhanov, Johnny Shields, and contributors.
|
|
279
|
+
|
|
280
|
+
MIT license, see [LICENSE](LICENSE.txt) for details.
|
data/Rakefile
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'bundler'
|
|
4
|
-
|
|
5
|
-
begin
|
|
6
|
-
Bundler.setup(:default, :development)
|
|
7
|
-
rescue Bundler::BundlerError => e
|
|
8
|
-
warn e.message
|
|
9
|
-
warn 'Run `bundle install` to install missing gems'
|
|
10
|
-
exit e.status_code
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
Bundler::GemHelper.install_tasks
|
|
14
|
-
|
|
15
|
-
require 'rspec/core'
|
|
16
|
-
require 'rspec/core/rake_task'
|
|
17
|
-
RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
18
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
require 'rubocop/rake_task'
|
|
22
|
-
RuboCop::RakeTask.new(:rubocop)
|
|
23
|
-
|
|
24
|
-
task default: %i[rubocop spec]
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler'
|
|
4
|
+
|
|
5
|
+
begin
|
|
6
|
+
Bundler.setup(:default, :development)
|
|
7
|
+
rescue Bundler::BundlerError => e
|
|
8
|
+
warn e.message
|
|
9
|
+
warn 'Run `bundle install` to install missing gems'
|
|
10
|
+
exit e.status_code
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
Bundler::GemHelper.install_tasks
|
|
14
|
+
|
|
15
|
+
require 'rspec/core'
|
|
16
|
+
require 'rspec/core/rake_task'
|
|
17
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
18
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require 'rubocop/rake_task'
|
|
22
|
+
RuboCop::RakeTask.new(:rubocop)
|
|
23
|
+
|
|
24
|
+
task default: %i[rubocop spec]
|
data/lib/config/locales/en.yml
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
en:
|
|
2
|
-
mongoid:
|
|
3
|
-
errors:
|
|
4
|
-
messages:
|
|
5
|
-
invalid_target_position:
|
|
6
|
-
message: "`%{value}` is not an acceptable value for target position."
|
|
7
|
-
summary: "Mongoid::Orderable accepts: a Numeric, a numeric string, :top, :bottom, or nil."
|
|
8
|
-
resolution: "You must give an acceptable value for target position."
|
|
9
|
-
transaction_failed:
|
|
10
|
-
message: "Transaction failed due to a database-level conflict."
|
|
11
|
-
summary: ""
|
|
12
|
-
resolution: "Please retry the transaction."
|
|
1
|
+
en:
|
|
2
|
+
mongoid:
|
|
3
|
+
errors:
|
|
4
|
+
messages:
|
|
5
|
+
invalid_target_position:
|
|
6
|
+
message: "`%{value}` is not an acceptable value for target position."
|
|
7
|
+
summary: "Mongoid::Orderable accepts: a Numeric, a numeric string, :top, :bottom, or nil."
|
|
8
|
+
resolution: "You must give an acceptable value for target position."
|
|
9
|
+
transaction_failed:
|
|
10
|
+
message: "Transaction failed due to a database-level conflict."
|
|
11
|
+
summary: ""
|
|
12
|
+
resolution: "Please retry the transaction."
|