mongoid-locker 0.2.1 → 0.3.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 +7 -0
- data/.rubocop.yml +38 -0
- data/.travis.yml +12 -20
- data/CHANGELOG.md +17 -1
- data/CONTRIBUTING.md +19 -0
- data/Gemfile +14 -9
- data/Guardfile +1 -1
- data/README.md +3 -18
- data/Rakefile +11 -9
- data/VERSION +1 -1
- data/lib/mongoid/locker.rb +59 -46
- data/lib/mongoid/locker/wrapper.rb +7 -7
- data/mongoid-locker.gemspec +22 -21
- data/spec/database2.yml +4 -0
- data/spec/{database.yml → database3.yml} +0 -4
- data/spec/database4.yml +7 -0
- data/spec/mongoid-locker_spec.rb +121 -62
- data/spec/spec_helper.rb +5 -2
- metadata +32 -59
- data/Appraisals +0 -7
- data/Gemfile.lock +0 -83
- data/gemfiles/mongoid2.gemfile +0 -24
- data/gemfiles/mongoid3.gemfile +0 -24
@@ -10,12 +10,12 @@ module Mongoid
|
|
10
10
|
# @param [Hash] The Mongoid query
|
11
11
|
# @param [Hash] The Mongoid setter
|
12
12
|
# @return [Boolean] true if the document was successfully updated, false otherwise
|
13
|
-
def self.update
|
13
|
+
def self.update(klass, query, setter)
|
14
14
|
error_obj =
|
15
15
|
if IS_OLD_MONGOID
|
16
|
-
klass.collection.update(query, setter, :
|
16
|
+
klass.collection.update(query, setter, safe: true)
|
17
17
|
else
|
18
|
-
klass.with(:
|
18
|
+
klass.with(safe: true).collection.find(query).update(setter)
|
19
19
|
end
|
20
20
|
|
21
21
|
error_obj['n'] == 1
|
@@ -25,14 +25,14 @@ module Mongoid
|
|
25
25
|
#
|
26
26
|
# @param [Class] The model instance
|
27
27
|
# @return [Time] The timestamp of when the document is locked until, nil if not locked.
|
28
|
-
def self.locked_until
|
28
|
+
def self.locked_until(doc)
|
29
29
|
existing_query = {
|
30
|
-
:
|
31
|
-
:
|
30
|
+
_id: doc.id,
|
31
|
+
locked_until: { '$exists' => true }
|
32
32
|
}
|
33
33
|
|
34
34
|
if IS_OLD_MONGOID
|
35
|
-
existing = doc.class.collection.find_one(existing_query, :
|
35
|
+
existing = doc.class.collection.find_one(existing_query, fields: { locked_until: 1 })
|
36
36
|
existing ? existing['locked_until'] : nil
|
37
37
|
else
|
38
38
|
existing = doc.class.where(existing_query).limit(1).only(:locked_until).first
|
data/mongoid-locker.gemspec
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: mongoid-locker 0.3.0 ruby lib
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
8
|
s.name = "mongoid-locker"
|
8
|
-
s.version = "0.
|
9
|
+
s.version = "0.3.0"
|
9
10
|
|
10
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
12
|
s.authors = ["Aidan Feldman"]
|
12
|
-
s.date = "
|
13
|
+
s.date = "2014-07-01"
|
13
14
|
s.description = "Allows multiple processes to operate on individual documents in MongoDB while ensuring that only one can act at a time."
|
14
15
|
s.email = "aidan.feldman@gmail.com"
|
15
16
|
s.extra_rdoc_files = [
|
@@ -19,11 +20,11 @@ Gem::Specification.new do |s|
|
|
19
20
|
s.files = [
|
20
21
|
".document",
|
21
22
|
".rspec",
|
23
|
+
".rubocop.yml",
|
22
24
|
".travis.yml",
|
23
|
-
"Appraisals",
|
24
25
|
"CHANGELOG.md",
|
26
|
+
"CONTRIBUTING.md",
|
25
27
|
"Gemfile",
|
26
|
-
"Gemfile.lock",
|
27
28
|
"Guardfile",
|
28
29
|
"LICENSE.txt",
|
29
30
|
"README.md",
|
@@ -35,53 +36,53 @@ Gem::Specification.new do |s|
|
|
35
36
|
"demo/instagram.png",
|
36
37
|
"demo/showoff.css",
|
37
38
|
"demo/showoff.md",
|
38
|
-
"gemfiles/mongoid2.gemfile",
|
39
|
-
"gemfiles/mongoid3.gemfile",
|
40
39
|
"lib/mongoid-locker.rb",
|
41
40
|
"lib/mongoid/locker.rb",
|
42
41
|
"lib/mongoid/locker/wrapper.rb",
|
43
42
|
"mongoid-locker.gemspec",
|
44
|
-
"spec/
|
43
|
+
"spec/database2.yml",
|
44
|
+
"spec/database3.yml",
|
45
|
+
"spec/database4.yml",
|
45
46
|
"spec/mongoid-locker_spec.rb",
|
46
47
|
"spec/spec_helper.rb"
|
47
48
|
]
|
48
49
|
s.homepage = "http://github.com/afeld/mongoid-locker"
|
49
50
|
s.licenses = ["MIT"]
|
50
51
|
s.require_paths = ["lib"]
|
51
|
-
s.rubygems_version = "1.
|
52
|
+
s.rubygems_version = "2.1.11"
|
52
53
|
s.summary = "Document-level locking for MongoDB via Mongoid"
|
53
54
|
|
54
55
|
if s.respond_to? :specification_version then
|
55
|
-
s.specification_version =
|
56
|
+
s.specification_version = 4
|
56
57
|
|
57
58
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
58
|
-
s.add_runtime_dependency(%q<mongoid>, ["
|
59
|
-
s.add_development_dependency(%q<rspec>, ["~>
|
59
|
+
s.add_runtime_dependency(%q<mongoid>, ["~> 4.0"])
|
60
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
60
61
|
s.add_development_dependency(%q<bundler>, ["~> 1.1"])
|
61
62
|
s.add_development_dependency(%q<jeweler>, ["~> 1.8"])
|
62
63
|
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
63
|
-
s.add_development_dependency(%q<
|
64
|
+
s.add_development_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
64
65
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
65
|
-
s.add_development_dependency(%q<
|
66
|
+
s.add_development_dependency(%q<rubocop>, ["= 0.24.0"])
|
66
67
|
else
|
67
|
-
s.add_dependency(%q<mongoid>, ["
|
68
|
-
s.add_dependency(%q<rspec>, ["~>
|
68
|
+
s.add_dependency(%q<mongoid>, ["~> 4.0"])
|
69
|
+
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
69
70
|
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
70
71
|
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
71
72
|
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
72
|
-
s.add_dependency(%q<
|
73
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
73
74
|
s.add_dependency(%q<rake>, [">= 0"])
|
74
|
-
s.add_dependency(%q<
|
75
|
+
s.add_dependency(%q<rubocop>, ["= 0.24.0"])
|
75
76
|
end
|
76
77
|
else
|
77
|
-
s.add_dependency(%q<mongoid>, ["
|
78
|
-
s.add_dependency(%q<rspec>, ["~>
|
78
|
+
s.add_dependency(%q<mongoid>, ["~> 4.0"])
|
79
|
+
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
79
80
|
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
80
81
|
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
81
82
|
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
82
|
-
s.add_dependency(%q<
|
83
|
+
s.add_dependency(%q<rb-fsevent>, ["~> 0.9.1"])
|
83
84
|
s.add_dependency(%q<rake>, [">= 0"])
|
84
|
-
s.add_dependency(%q<
|
85
|
+
s.add_dependency(%q<rubocop>, ["= 0.24.0"])
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
data/spec/database2.yml
ADDED
data/spec/database4.yml
ADDED
data/spec/mongoid-locker_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
2
|
|
3
3
|
describe Mongoid::Locker do
|
4
|
-
def remove_class
|
4
|
+
def remove_class(klass)
|
5
5
|
Object.send :remove_const, klass.to_s.to_sym
|
6
6
|
end
|
7
7
|
|
@@ -11,10 +11,10 @@ describe Mongoid::Locker do
|
|
11
11
|
include Mongoid::Document
|
12
12
|
include Mongoid::Locker
|
13
13
|
|
14
|
-
field :account_balance, :
|
14
|
+
field :account_balance, type: Integer # easier to test than Float
|
15
15
|
end
|
16
16
|
|
17
|
-
@user = User.create! :
|
17
|
+
@user = User.create! account_balance: 20
|
18
18
|
end
|
19
19
|
|
20
20
|
after do
|
@@ -22,70 +22,69 @@ describe Mongoid::Locker do
|
|
22
22
|
remove_class User
|
23
23
|
end
|
24
24
|
|
25
|
-
|
26
|
-
describe "#locked?" do
|
25
|
+
describe '#locked?' do
|
27
26
|
it "shouldn't be locked when created" do
|
28
|
-
@user.locked
|
27
|
+
expect(@user.locked?).to be false
|
29
28
|
end
|
30
29
|
|
31
|
-
it
|
30
|
+
it 'should be true when locked' do
|
32
31
|
@user.with_lock do
|
33
|
-
@user.locked
|
32
|
+
expect(@user.locked?).to be true
|
34
33
|
end
|
35
34
|
end
|
36
35
|
|
37
|
-
it
|
36
|
+
it 'should respect the expiration' do
|
38
37
|
User.timeout_lock_after 1
|
39
38
|
|
40
39
|
@user.with_lock do
|
41
40
|
sleep 2
|
42
|
-
@user.locked
|
41
|
+
expect(@user.locked?).to be false
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
|
-
it
|
45
|
+
it 'should be true for a different instance' do
|
47
46
|
@user.with_lock do
|
48
|
-
User.first.locked
|
47
|
+
expect(User.first.locked?).to be true
|
49
48
|
end
|
50
49
|
end
|
51
50
|
end
|
52
51
|
|
53
|
-
describe
|
52
|
+
describe '#has_lock?' do
|
54
53
|
it "shouldn't be has_lock when created" do
|
55
|
-
@user.has_lock
|
54
|
+
expect(@user.has_lock?).to be false
|
56
55
|
end
|
57
56
|
|
58
|
-
it
|
57
|
+
it 'should be true when has_lock' do
|
59
58
|
@user.with_lock do
|
60
|
-
@user.has_lock
|
59
|
+
expect(@user.has_lock?).to be true
|
61
60
|
end
|
62
61
|
end
|
63
62
|
|
64
|
-
it
|
63
|
+
it 'should respect the expiration' do
|
65
64
|
User.timeout_lock_after 1
|
66
65
|
|
67
66
|
@user.with_lock do
|
68
67
|
sleep 2
|
69
|
-
@user.has_lock
|
68
|
+
expect(@user.has_lock?).to be false
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
73
|
-
it
|
72
|
+
it 'should be false for a different instance' do
|
74
73
|
@user.with_lock do
|
75
|
-
User.first.has_lock
|
74
|
+
expect(User.first.has_lock?).to be false
|
76
75
|
end
|
77
76
|
end
|
78
77
|
end
|
79
78
|
|
80
|
-
describe
|
81
|
-
it
|
79
|
+
describe '#with_lock' do
|
80
|
+
it 'should lock and unlock the user' do
|
82
81
|
@user.with_lock do
|
83
|
-
@user.
|
84
|
-
User.first.
|
82
|
+
expect(@user).to be_locked
|
83
|
+
expect(User.first).to be_locked
|
85
84
|
end
|
86
85
|
|
87
|
-
@user.
|
88
|
-
@user.reload.
|
86
|
+
expect(@user).to_not be_locked
|
87
|
+
expect(@user.reload).to_not be_locked
|
89
88
|
end
|
90
89
|
|
91
90
|
it "shouldn't save the full document" do
|
@@ -93,21 +92,21 @@ describe Mongoid::Locker do
|
|
93
92
|
@user.account_balance = 10
|
94
93
|
end
|
95
94
|
|
96
|
-
@user.account_balance.
|
97
|
-
User.first.account_balance.
|
95
|
+
expect(@user.account_balance).to eq(10)
|
96
|
+
expect(User.first.account_balance).to eq(20)
|
98
97
|
end
|
99
98
|
|
100
|
-
it
|
99
|
+
it 'should handle errors gracefully' do
|
101
100
|
expect {
|
102
101
|
@user.with_lock do
|
103
|
-
|
102
|
+
fail 'booyah!'
|
104
103
|
end
|
105
104
|
}.to raise_error
|
106
105
|
|
107
|
-
@user.reload.
|
106
|
+
expect(@user.reload).to_not be_locked
|
108
107
|
end
|
109
108
|
|
110
|
-
it
|
109
|
+
it 'should complain if trying to lock locked doc' do
|
111
110
|
@user.with_lock do
|
112
111
|
user_dup = User.first
|
113
112
|
|
@@ -115,45 +114,105 @@ describe Mongoid::Locker do
|
|
115
114
|
user_dup.with_lock do
|
116
115
|
fail "shouldn't get the lock"
|
117
116
|
end
|
118
|
-
}.to raise_error(Mongoid::LockError)
|
117
|
+
}.to raise_error(Mongoid::Locker::LockError)
|
119
118
|
end
|
120
119
|
end
|
121
120
|
|
122
|
-
it
|
121
|
+
it 'should handle recursive calls' do
|
123
122
|
@user.with_lock do
|
124
123
|
@user.with_lock do
|
125
124
|
@user.account_balance = 10
|
126
125
|
end
|
127
126
|
end
|
128
127
|
|
129
|
-
@user.account_balance.
|
128
|
+
expect(@user.account_balance).to eq(10)
|
130
129
|
end
|
131
130
|
|
132
|
-
it
|
131
|
+
it 'should wait until the lock times out, if desired' do
|
133
132
|
User.timeout_lock_after 1
|
134
133
|
|
135
134
|
@user.with_lock do
|
136
135
|
user_dup = User.first
|
137
136
|
|
138
|
-
user_dup.with_lock :
|
137
|
+
user_dup.with_lock wait: true do
|
139
138
|
user_dup.account_balance = 10
|
140
139
|
user_dup.save!
|
141
140
|
end
|
142
141
|
end
|
143
142
|
|
144
|
-
@user.reload.account_balance.
|
143
|
+
expect(@user.reload.account_balance).to eq(10)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should, by default, reload the row after acquiring the lock' do
|
147
|
+
expect(@user).to receive(:reload)
|
148
|
+
@user.with_lock do
|
149
|
+
# no-op
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should allow override of the default reload behavior' do
|
154
|
+
expect(@user).to_not receive(:reload)
|
155
|
+
@user.with_lock reload: false do
|
156
|
+
# no-op
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'should, by default, not retry' do
|
161
|
+
expect(@user).to receive(:acquire_lock).once.and_return(true)
|
162
|
+
@user.with_lock do
|
163
|
+
user_dup = User.first
|
164
|
+
|
165
|
+
user_dup.with_lock do
|
166
|
+
# no-op
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'should retry the number of times given, if desired' do
|
172
|
+
allow(@user).to receive(:acquire_lock).and_return(false)
|
173
|
+
allow(Mongoid::Locker::Wrapper).to receive(:locked_until).and_return(Time.now)
|
174
|
+
|
175
|
+
expect(@user).to receive(:acquire_lock).exactly(6).times
|
176
|
+
expect {
|
177
|
+
@user.with_lock retries: 5 do
|
178
|
+
# no-op
|
179
|
+
end
|
180
|
+
}.to raise_error(Mongoid::Locker::LockError)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should, by default, when retrying, sleep until the lock expires' do
|
184
|
+
allow(@user).to receive(:acquire_lock).and_return(false)
|
185
|
+
allow(Mongoid::Locker::Wrapper).to receive(:locked_until).and_return(Time.now + 5.seconds)
|
186
|
+
allow(@user).to receive(:sleep) { |time| expect(time).to be_within(0.1).of(5) }
|
187
|
+
|
188
|
+
expect {
|
189
|
+
@user.with_lock retries: 1 do
|
190
|
+
# no-op
|
191
|
+
end
|
192
|
+
}.to raise_error(Mongoid::Locker::LockError)
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should sleep for the time given, if desired' do
|
196
|
+
allow(@user).to receive(:acquire_lock).and_return(false)
|
197
|
+
allow(@user).to receive(:sleep) { |time| expect(time).to be_within(0.1).of(3) }
|
198
|
+
|
199
|
+
expect {
|
200
|
+
@user.with_lock(retries: 1, retry_sleep: 3) do
|
201
|
+
# no-op
|
202
|
+
end
|
203
|
+
}.to raise_error(Mongoid::Locker::LockError)
|
145
204
|
end
|
146
205
|
|
147
|
-
it
|
206
|
+
it 'should override the default timeout' do
|
148
207
|
User.timeout_lock_after 1
|
149
208
|
|
150
209
|
expiration = (Time.now + 3).to_i
|
151
|
-
@user.with_lock :
|
152
|
-
@user.locked_until.to_i.
|
210
|
+
@user.with_lock timeout: 3 do
|
211
|
+
expect(@user.locked_until.to_i).to eq(expiration)
|
153
212
|
end
|
154
213
|
end
|
155
214
|
|
156
|
-
it
|
215
|
+
it 'should reload the document if it needs to wait for a lock' do
|
157
216
|
User.timeout_lock_after 1
|
158
217
|
|
159
218
|
@user.with_lock do
|
@@ -162,33 +221,33 @@ describe Mongoid::Locker do
|
|
162
221
|
@user.account_balance = 10
|
163
222
|
@user.save!
|
164
223
|
|
165
|
-
user_dup.account_balance.
|
166
|
-
user_dup.with_lock :
|
167
|
-
user_dup.account_balance.
|
224
|
+
expect(user_dup.account_balance).to eq(20)
|
225
|
+
user_dup.with_lock wait: true do
|
226
|
+
expect(user_dup.account_balance).to eq(10)
|
168
227
|
end
|
169
228
|
end
|
170
229
|
end
|
171
230
|
|
172
|
-
it
|
231
|
+
it 'should succeed for subclasses' do
|
173
232
|
class Admin < User
|
174
233
|
end
|
175
234
|
|
176
235
|
admin = Admin.create!
|
177
236
|
|
178
237
|
admin.with_lock do
|
179
|
-
admin.
|
180
|
-
Admin.first.
|
238
|
+
expect(admin).to be_locked
|
239
|
+
expect(Admin.first).to be_locked
|
181
240
|
end
|
182
241
|
|
183
|
-
admin.
|
184
|
-
admin.reload.
|
242
|
+
expect(admin).to_not be_locked
|
243
|
+
expect(admin.reload).to_not be_locked
|
185
244
|
|
186
245
|
remove_class Admin
|
187
246
|
end
|
188
247
|
end
|
189
248
|
|
190
|
-
describe
|
191
|
-
it
|
249
|
+
describe '.timeout_lock_after' do
|
250
|
+
it 'should ignore the lock if it has timed out' do
|
192
251
|
User.timeout_lock_after 1
|
193
252
|
|
194
253
|
@user.with_lock do
|
@@ -201,10 +260,10 @@ describe Mongoid::Locker do
|
|
201
260
|
end
|
202
261
|
end
|
203
262
|
|
204
|
-
@user.reload.account_balance.
|
263
|
+
expect(@user.reload.account_balance).to eq(10)
|
205
264
|
end
|
206
265
|
|
207
|
-
it
|
266
|
+
it 'should be independent for different classes' do
|
208
267
|
class Account
|
209
268
|
include Mongoid::Document
|
210
269
|
include Mongoid::Locker
|
@@ -213,28 +272,28 @@ describe Mongoid::Locker do
|
|
213
272
|
User.timeout_lock_after 1
|
214
273
|
Account.timeout_lock_after 2
|
215
274
|
|
216
|
-
User.lock_timeout.
|
275
|
+
expect(User.lock_timeout).to eq(1)
|
217
276
|
|
218
277
|
remove_class Account
|
219
278
|
end
|
220
279
|
end
|
221
280
|
|
222
|
-
describe
|
223
|
-
it
|
224
|
-
|
281
|
+
describe '.locked' do
|
282
|
+
it 'should return the locked documents' do
|
283
|
+
User.create!
|
225
284
|
|
226
285
|
@user.with_lock do
|
227
|
-
User.locked.to_a.
|
286
|
+
expect(User.locked.to_a).to eq([@user])
|
228
287
|
end
|
229
288
|
end
|
230
289
|
end
|
231
290
|
|
232
|
-
describe
|
233
|
-
it
|
291
|
+
describe '.unlocked' do
|
292
|
+
it 'should return the unlocked documents' do
|
234
293
|
user2 = User.create!
|
235
294
|
|
236
295
|
@user.with_lock do
|
237
|
-
User.unlocked.to_a.
|
296
|
+
expect(User.unlocked.to_a).to eq([user2])
|
238
297
|
end
|
239
298
|
end
|
240
299
|
end
|