mongoid-locker 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mongoid
2
4
  module Locker
3
- VERSION = '1.0.1'.freeze
5
+ VERSION = '2.0.0'
4
6
  end
5
7
  end
@@ -1,3 +1,121 @@
1
- raise "Incompatible Mongoid #{version} version" unless %w[4 5 6 7].include?(version = Mongoid::VERSION.split('.')[0])
1
+ # frozen_string_literal: true
2
2
 
3
- require "mongoid/locker/wrapper#{version}"
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
@@ -1,20 +1,22 @@
1
- lib = File.expand_path('lib', __dir__)
2
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
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 |spec|
6
- spec.name = 'mongoid-locker'
7
- spec.version = Mongoid::Locker::VERSION
8
- spec.authors = ['Aidan Feldman']
9
- spec.email = ['aidan.feldman@gmail.com']
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
- spec.summary = 'Document-level locking for MongoDB via Mongoid.'
12
- spec.description = 'Allows multiple processes to operate on individual documents in MongoDB while ensuring that only one can act at a time.'
13
- spec.homepage = 'https://github.com/mongoid/mongoid-locker'
14
- spec.license = 'MIT'
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
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|demo)/}) }
17
- spec.require_paths = ['lib']
18
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
19
+ s.require_paths = ['lib']
18
20
 
19
- spec.add_runtime_dependency 'mongoid', '>= 4.0', '< 8'
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: 1.0.1
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-03-23 00:00:00.000000000 Z
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: '4.0'
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: '4.0'
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,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
@@ -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
@@ -1,2 +0,0 @@
1
- # same with wrapper6
2
- require 'mongoid/locker/wrapper6'