mongoid-locker 1.0.1 → 2.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -7
- data/.rspec +1 -2
- data/.rubocop_todo.yml +39 -23
- data/.travis.yml +27 -41
- data/CHANGELOG.md +6 -0
- data/Dangerfile +2 -0
- data/Gemfile +10 -10
- data/Guardfile +2 -0
- data/LICENSE.txt +1 -1
- data/README.md +88 -36
- data/Rakefile +2 -0
- data/UPGRADING.md +20 -1
- data/lib/config/locales/en.yml +9 -0
- data/lib/mongoid-locker.rb +10 -1
- data/lib/mongoid/locker.rb +243 -142
- data/lib/mongoid/locker/errors.rb +46 -0
- data/lib/mongoid/locker/version.rb +3 -1
- data/lib/mongoid/locker/wrapper.rb +120 -2
- data/mongoid-locker.gemspec +16 -14
- metadata +7 -10
- data/.document +0 -5
- data/lib/mongoid/locker/wrapper4.rb +0 -22
- data/lib/mongoid/locker/wrapper5.rb +0 -27
- data/lib/mongoid/locker/wrapper6.rb +0 -29
- data/lib/mongoid/locker/wrapper7.rb +0 -2
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module Locker
|
5
|
+
module Errors
|
6
|
+
# Default parent Mongoid::Locker error for all custom errors.
|
7
|
+
class MongoidLockerError < StandardError
|
8
|
+
BASE_KEY = 'mongoid.locker.errors.messages'
|
9
|
+
|
10
|
+
def initialize(key, **params)
|
11
|
+
message = I18n.translate("#{BASE_KEY}.#{key}.message", params)
|
12
|
+
|
13
|
+
super("\nmessage:\n #{message}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when a document could not be successfully locked in the database.
|
18
|
+
class DocumentCouldNotGetLock < MongoidLockerError
|
19
|
+
KEY = 'document_could_not_get_lock'
|
20
|
+
|
21
|
+
# @example Create new error.
|
22
|
+
# DocumentCouldNotGetLock.new(Account, '1234')
|
23
|
+
#
|
24
|
+
# @param klass [Class] the model class
|
25
|
+
# @param id [String, BSON::ObjectId] the document's id
|
26
|
+
def initialize(klass, id)
|
27
|
+
super(KEY, klass: klass, id: id.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when trying to pass an invalid parameter to locker method by a class.
|
32
|
+
class InvalidParameter < MongoidLockerError
|
33
|
+
KEY = 'invalid_parameter'
|
34
|
+
|
35
|
+
# @example Create new error.
|
36
|
+
# InvalidParameter.new(User, :lock_timeout)
|
37
|
+
#
|
38
|
+
# @param klass [Class] the model class
|
39
|
+
# @param parameter [String, Symbol] the class parameter
|
40
|
+
def initialize(klass, parameter)
|
41
|
+
super(KEY, klass: klass, parameter: parameter)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,3 +1,121 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module Mongoid
|
4
|
+
module Locker
|
5
|
+
# Set of methods to interact with the database.
|
6
|
+
module Wrapper
|
7
|
+
# Finds provided document with provided options, locks (sets +locking_name_field+ and +locked_at_field+ fields), and returns the fields.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# Mongoid::Locker::Wrapper.find_and_lock(document, opts)
|
11
|
+
# #=> {"locked_at"=>2019-03-19 07:51:24 UTC, "locking_name"=>nil}
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# Mongoid::Locker::Wrapper.find_and_lock(document, opts)
|
15
|
+
# # => nil
|
16
|
+
#
|
17
|
+
# @param doc [Mongoid::Document]
|
18
|
+
# @param opts [Hash] (see #with_lock)
|
19
|
+
# @return [Hash] with +locking_name_field+ and +locked_at_field+ fields
|
20
|
+
# @return [nil] if the document was not found, was already locked
|
21
|
+
def self.find_and_lock(doc, opts)
|
22
|
+
model = doc.class
|
23
|
+
filter = {
|
24
|
+
_id: doc.id,
|
25
|
+
'$or': [
|
26
|
+
{
|
27
|
+
'$or': [
|
28
|
+
{ model.locking_name_field => { '$exists': false } },
|
29
|
+
{ model.locked_at_field => { '$exists': false } }
|
30
|
+
]
|
31
|
+
},
|
32
|
+
{
|
33
|
+
'$or': [
|
34
|
+
{ model.locking_name_field => { '$eq': nil } },
|
35
|
+
{ model.locked_at_field => { '$eq': nil } }
|
36
|
+
]
|
37
|
+
},
|
38
|
+
{
|
39
|
+
'$where': "new Date() - this.#{model.locked_at_field} >= #{model.lock_timeout * 1000}"
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
update = {
|
44
|
+
'$set': { model.locking_name_field => opts[:locking_name] },
|
45
|
+
'$currentDate': { model.locked_at_field => true }
|
46
|
+
}
|
47
|
+
options = {
|
48
|
+
return_document: :after,
|
49
|
+
projection: { _id: false, model.locking_name_field => true, model.locked_at_field => true },
|
50
|
+
write_concern: model.locker_write_concern
|
51
|
+
}
|
52
|
+
|
53
|
+
model.collection.find_one_and_update(filter, update, options)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Finds provided document with provided options, unlocks (sets +locking_name_field+ and +locked_at_field+ fields to +nil+).
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# Mongoid::Locker::Wrapper.find_and_unlock(doc, opts)
|
60
|
+
# #=> true
|
61
|
+
# Mongoid::Locker::Wrapper.find_and_unlock(doc, opts)
|
62
|
+
# #=> false
|
63
|
+
#
|
64
|
+
# @param doc [Mongoid::Document]
|
65
|
+
# @param opts [Hash] (see #with_lock)
|
66
|
+
# @return [Boolean]
|
67
|
+
# @return [true] if the document was unlocked
|
68
|
+
# @return [false] if the document was not found, was not unlocked
|
69
|
+
def self.find_and_unlock(doc, opts)
|
70
|
+
model = doc.class
|
71
|
+
filter = {
|
72
|
+
_id: doc.id,
|
73
|
+
model.locking_name_field => opts[:locking_name]
|
74
|
+
}
|
75
|
+
update = {
|
76
|
+
'$set': {
|
77
|
+
model.locking_name_field => nil,
|
78
|
+
model.locked_at_field => nil
|
79
|
+
}
|
80
|
+
}
|
81
|
+
options = { write_concern: model.locker_write_concern }
|
82
|
+
|
83
|
+
result = model.collection.update_one(filter, update, options)
|
84
|
+
result.ok? && result.written_count == 1
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns value of +locked_at_field+ field for provided document.
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# Mongoid::Locker::Wrapper.locked_at(document)
|
91
|
+
# #=> 2019-06-03 13:50:46 UTC
|
92
|
+
#
|
93
|
+
# @param doc [Mongoid::Document]
|
94
|
+
# @return [Time] +locked_at_field+ field time
|
95
|
+
# @return [nil] if response was failed
|
96
|
+
def self.locked_at(doc)
|
97
|
+
result = doc.class.collection.find(
|
98
|
+
{ _id: doc.id },
|
99
|
+
projection: { _id: false, doc.locked_at_field => true },
|
100
|
+
limit: 1
|
101
|
+
).first
|
102
|
+
|
103
|
+
result[doc.locked_at_field.to_s] if result
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the local database server time in UTC.
|
107
|
+
#
|
108
|
+
# @example
|
109
|
+
# Mongoid::Locker::Wrapper.current_mongodb_time(User)
|
110
|
+
# #=> 2019-03-19 07:24:36 UTC
|
111
|
+
#
|
112
|
+
# @param model [Class] the model class
|
113
|
+
# @return [Time] current time
|
114
|
+
# @return [nil] if response was failed
|
115
|
+
def self.current_mongodb_time(model)
|
116
|
+
info = model.collection.database.command(isMaster: 1)
|
117
|
+
info.ok? ? info.documents.first['localTime'] : nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/mongoid-locker.gemspec
CHANGED
@@ -1,20 +1,22 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
|
3
5
|
require 'mongoid/locker/version'
|
4
6
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'mongoid-locker'
|
9
|
+
s.version = Mongoid::Locker::VERSION
|
10
|
+
s.authors = ['Aidan Feldman']
|
11
|
+
s.email = ['aidan.feldman@gmail.com']
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
s.summary = 'Document-level optimistic locking for MongoDB via Mongoid.'
|
14
|
+
s.description = 'Allows multiple processes to operate on individual documents in MongoDB while ensuring that only one can act at a time.'
|
15
|
+
s.homepage = 'https://github.com/mongoid/mongoid-locker'
|
16
|
+
s.license = 'MIT'
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
|
19
|
+
s.require_paths = ['lib']
|
18
20
|
|
19
|
-
|
21
|
+
s.add_dependency 'mongoid', '>= 5.0', '< 8'
|
20
22
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-locker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aidan Feldman
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -16,7 +16,7 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '5.0'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '8'
|
@@ -26,7 +26,7 @@ dependencies:
|
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '5.0'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '8'
|
@@ -38,7 +38,6 @@ executables: []
|
|
38
38
|
extensions: []
|
39
39
|
extra_rdoc_files: []
|
40
40
|
files:
|
41
|
-
- ".document"
|
42
41
|
- ".gitignore"
|
43
42
|
- ".rspec"
|
44
43
|
- ".rubocop.yml"
|
@@ -54,14 +53,12 @@ files:
|
|
54
53
|
- RELEASING.md
|
55
54
|
- Rakefile
|
56
55
|
- UPGRADING.md
|
56
|
+
- lib/config/locales/en.yml
|
57
57
|
- lib/mongoid-locker.rb
|
58
58
|
- lib/mongoid/locker.rb
|
59
|
+
- lib/mongoid/locker/errors.rb
|
59
60
|
- lib/mongoid/locker/version.rb
|
60
61
|
- lib/mongoid/locker/wrapper.rb
|
61
|
-
- lib/mongoid/locker/wrapper4.rb
|
62
|
-
- lib/mongoid/locker/wrapper5.rb
|
63
|
-
- lib/mongoid/locker/wrapper6.rb
|
64
|
-
- lib/mongoid/locker/wrapper7.rb
|
65
62
|
- mongoid-locker.gemspec
|
66
63
|
homepage: https://github.com/mongoid/mongoid-locker
|
67
64
|
licenses:
|
@@ -85,5 +82,5 @@ requirements: []
|
|
85
82
|
rubygems_version: 3.0.3
|
86
83
|
signing_key:
|
87
84
|
specification_version: 4
|
88
|
-
summary: Document-level locking for MongoDB via Mongoid.
|
85
|
+
summary: Document-level optimistic locking for MongoDB via Mongoid.
|
89
86
|
test_files: []
|
data/.document
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module Locker
|
3
|
-
# Normalizes queries between various Mongoid versions.
|
4
|
-
module Wrapper
|
5
|
-
# Update the document for the provided Class matching the provided query with the provided setter.
|
6
|
-
#
|
7
|
-
# @param [Class] The model class
|
8
|
-
# @param [Hash] The Mongoid query
|
9
|
-
# @param [Hash] The Mongoid setter
|
10
|
-
# @return [Boolean] true if the document was successfully updated, false otherwise
|
11
|
-
def self.update(klass, query, setter)
|
12
|
-
klass.with(safe: true).collection.find(query).update(setter)['n'] == 1
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.locked_until(doc)
|
16
|
-
existing_query = { _id: doc.id, doc.locked_until_field => { '$exists' => true } }
|
17
|
-
existing = doc.class.where(existing_query).limit(1).only(doc.locked_until_field).first
|
18
|
-
existing ? existing[doc.locked_until_field] : nil
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module Locker
|
3
|
-
# Normalizes queries between various Mongoid versions.
|
4
|
-
module Wrapper
|
5
|
-
# Update the document for the provided Class matching the provided query with the provided setter.
|
6
|
-
#
|
7
|
-
# @param [Class] The model class
|
8
|
-
# @param [Hash] The Mongoid query
|
9
|
-
# @param [Hash] The Mongoid setter
|
10
|
-
# @return [Boolean] true if the document was successfully updated, false otherwise
|
11
|
-
def self.update(klass, query, setter)
|
12
|
-
rc = klass.with(write: { w: 1 }).collection.find(query).update_one(setter)
|
13
|
-
rc.ok? && rc.documents.first['n'] == 1
|
14
|
-
end
|
15
|
-
|
16
|
-
# Determine whether the provided document is locked in the database or not.
|
17
|
-
#
|
18
|
-
# @param [Class] The model instance
|
19
|
-
# @return [Time] The timestamp of when the document is locked until, nil if not locked.
|
20
|
-
def self.locked_until(doc)
|
21
|
-
existing_query = { _id: doc.id, doc.locked_until_field => { '$exists' => true } }
|
22
|
-
existing = doc.class.where(existing_query).limit(1).only(doc.locked_until_field).first
|
23
|
-
existing ? existing[doc.locked_until_field] : nil
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Mongoid
|
2
|
-
module Locker
|
3
|
-
# Normalizes queries between various Mongoid versions.
|
4
|
-
module Wrapper
|
5
|
-
# Update the document for the provided Class matching the provided query with the provided setter.
|
6
|
-
#
|
7
|
-
# @param [Class] The model class
|
8
|
-
# @param [Hash] The Mongoid query
|
9
|
-
# @param [Hash] The Mongoid setter
|
10
|
-
# @return [Boolean] true if the document was successfully updated, false otherwise
|
11
|
-
def self.update(klass, query, setter)
|
12
|
-
rc = klass.with(write: { w: 1 }) do
|
13
|
-
klass.collection.find(query).update_one(setter)
|
14
|
-
end
|
15
|
-
rc.ok? && rc.documents.first['n'] == 1
|
16
|
-
end
|
17
|
-
|
18
|
-
# Determine whether the provided document is locked in the database or not.
|
19
|
-
#
|
20
|
-
# @param [Class] The model instance
|
21
|
-
# @return [Time] The timestamp of when the document is locked until, nil if not locked.
|
22
|
-
def self.locked_until(doc)
|
23
|
-
existing_query = { _id: doc.id, doc.locked_until_field => { '$exists' => true } }
|
24
|
-
existing = doc.class.where(existing_query).limit(1).only(doc.locked_until_field).first
|
25
|
-
existing ? existing[doc.locked_until_field] : nil
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|