mongoid-locker 0.1.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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +20 -0
- data/Guardfile +10 -0
- data/LICENSE.txt +20 -0
- data/README.md +66 -0
- data/Rakefile +34 -0
- data/VERSION +1 -0
- data/lib/mongoid-locker.rb +2 -0
- data/lib/mongoid/locker.rb +141 -0
- data/mongoid-locker.gemspec +69 -0
- data/spec/database.yml +3 -0
- data/spec/mongoid-locker_spec.rb +159 -0
- data/spec/spec_helper.rb +14 -0
- metadata +162 -0
data/.document
ADDED
data/.rspec
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
source 'http://rubygems.org'
|
|
2
|
+
# Add dependencies required to use your gem here.
|
|
3
|
+
# Example:
|
|
4
|
+
# gem 'activesupport', '>= 2.3.5'
|
|
5
|
+
|
|
6
|
+
gem 'mongoid', '~> 2.4'
|
|
7
|
+
|
|
8
|
+
# Add dependencies to develop your gem here.
|
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
|
10
|
+
group :development do
|
|
11
|
+
gem 'rspec', '~> 2.8'
|
|
12
|
+
gem 'bundler', '~> 1.1'
|
|
13
|
+
gem 'jeweler', '~> 1.8'
|
|
14
|
+
|
|
15
|
+
gem 'guard-rspec'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
group :development, :test do
|
|
19
|
+
gem 'bson_ext', :platforms => :ruby
|
|
20
|
+
end
|
data/Guardfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# More info at https://github.com/guard/guard#readme
|
|
2
|
+
|
|
3
|
+
guard 'rspec', :version => 2 do
|
|
4
|
+
watch(%r{^spec/.+_spec\.rb$})
|
|
5
|
+
|
|
6
|
+
watch('Gemfile') { "spec" }
|
|
7
|
+
watch('Gemfile.lock') { "spec" }
|
|
8
|
+
watch(%r{^lib/(.+)\.rb$}) { "spec" }
|
|
9
|
+
watch('spec/spec_helper.rb') { "spec" }
|
|
10
|
+
end
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2012 Aidan Feldman
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# mongoid-locker [](http://travis-ci.org/afeld/mongoid-locker)
|
|
2
|
+
|
|
3
|
+
Document-level locking for MongoDB via Mongoid. The need arose at [Jux](https://jux.com) from multiple processes on multiple servers trying to act upon the same document and stepping on each other's toes. Mongoid-Locker is an easy way to ensure only one process can perform a certain operation on a document at a time.
|
|
4
|
+
|
|
5
|
+
[Tested](http://travis-ci.org/afeld/mongoid-locker) against MRI 1.8.7, 1.9.2 and 1.9.3, Rubinius 1.8 and 1.9, and JRuby 1.8 and 1.9.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Add to your `Gemfile`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'mongoid-locker', '~> 0.1.0'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
and run `bundle install`. In the model you wish to lock, include `Mongoid::Locker` after `Mongoid::Document`. For example:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
class QueueItem
|
|
19
|
+
include Mongoid::Document
|
|
20
|
+
include Mongoid::Locker
|
|
21
|
+
|
|
22
|
+
field :completed_at, :type => Time
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then, execute any code you like in a block like so:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
queue_item.with_lock do
|
|
30
|
+
|
|
31
|
+
# do stuff
|
|
32
|
+
|
|
33
|
+
queue_item.completed_at = Time.now
|
|
34
|
+
queue_item.save!
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`#with_lock` takes a couple options as a hash:
|
|
39
|
+
|
|
40
|
+
* `timeout`: The amount of time until a lock expires, in seconds. Defaults to `5`.
|
|
41
|
+
* `wait`: If a lock exists on the document, wait until that lock expires and try again. Defaults to `false`.
|
|
42
|
+
|
|
43
|
+
The default timeout can also be set on a per-class basis:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
class QueueItem
|
|
47
|
+
# ...
|
|
48
|
+
timeout_lock_after 10
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Note that these locks are only enforced when using `#with_lock`, not at the database level. It is useful for transactional operations, where you can make atomic modification of the document with checks. For exmple, you could deduct a purchase from a user's balance... _unless_ they are broke.
|
|
53
|
+
|
|
54
|
+
More in-depth method documentation can be found at [rdoc.info](http://rdoc.info/github/afeld/mongoid-locker/frames). Enjoy!
|
|
55
|
+
|
|
56
|
+
## Contributing
|
|
57
|
+
|
|
58
|
+
Pull requests are welcome. To run tests:
|
|
59
|
+
|
|
60
|
+
$ bundle install
|
|
61
|
+
$ rake
|
|
62
|
+
|
|
63
|
+
To auto-run tests as you code:
|
|
64
|
+
|
|
65
|
+
$ bundle install
|
|
66
|
+
$ guard
|
data/Rakefile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'rubygems'
|
|
4
|
+
require 'bundler'
|
|
5
|
+
begin
|
|
6
|
+
Bundler.setup(:default, :development)
|
|
7
|
+
rescue Bundler::BundlerError => e
|
|
8
|
+
$stderr.puts e.message
|
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
|
10
|
+
exit e.status_code
|
|
11
|
+
end
|
|
12
|
+
require 'rake'
|
|
13
|
+
|
|
14
|
+
require 'jeweler'
|
|
15
|
+
Jeweler::Tasks.new do |gem|
|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
|
17
|
+
gem.name = "mongoid-locker"
|
|
18
|
+
gem.homepage = "http://github.com/afeld/mongoid-locker"
|
|
19
|
+
gem.license = "MIT"
|
|
20
|
+
gem.summary = "Document-level locking for MongoDB via Mongoid"
|
|
21
|
+
gem.description = %Q{Allows multiple processes to operate on individual documents in MongoDB while ensuring that only one can act at a time.}
|
|
22
|
+
gem.email = "aidan.feldman@gmail.com"
|
|
23
|
+
gem.authors = ["Aidan Feldman"]
|
|
24
|
+
# dependencies defined in Gemfile
|
|
25
|
+
end
|
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
|
27
|
+
|
|
28
|
+
require 'rspec/core'
|
|
29
|
+
require 'rspec/core/rake_task'
|
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
task :default => :spec
|
data/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
module Mongoid
|
|
2
|
+
module Locker
|
|
3
|
+
module ClassMethods
|
|
4
|
+
# A scope to retrieve all locked documents in the collection.
|
|
5
|
+
#
|
|
6
|
+
# @return [Mongoid::Criteria]
|
|
7
|
+
def locked
|
|
8
|
+
where :locked_until.gt => Time.now
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# A scope to retrieve all unlocked documents in the collection.
|
|
12
|
+
#
|
|
13
|
+
# @return [Mongoid::Criteria]
|
|
14
|
+
def unlocked
|
|
15
|
+
any_of({:locked_until => nil}, {:locked_until.lte => Time.now})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Set the default lock timeout for this class. Note this only applies to new locks. Defaults to five seconds.
|
|
19
|
+
#
|
|
20
|
+
# @param [Fixnum] new_time the default number of seconds until a lock is considered "expired", in seconds
|
|
21
|
+
# @return [void]
|
|
22
|
+
def timeout_lock_after new_time
|
|
23
|
+
@lock_timeout = new_time
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Retrieve the lock timeout default for this class.
|
|
27
|
+
#
|
|
28
|
+
# @return [Fixnum] the default number of seconds until a lock is considered "expired", in seconds
|
|
29
|
+
def lock_timeout
|
|
30
|
+
@lock_timeout
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api private
|
|
35
|
+
def self.included mod
|
|
36
|
+
mod.extend ClassMethods
|
|
37
|
+
|
|
38
|
+
mod.field :locked_at, :type => Time
|
|
39
|
+
mod.field :locked_until, :type => Time
|
|
40
|
+
|
|
41
|
+
# default timeout of five seconds
|
|
42
|
+
mod.instance_variable_set :@lock_timeout, 5
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# Returns whether the document is currently locked or not.
|
|
47
|
+
#
|
|
48
|
+
# @return [Boolean] true if locked, false otherwise
|
|
49
|
+
def locked?
|
|
50
|
+
self.locked_until && self.locked_until > Time.now
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Primary method of plugin: execute the provided code once the document has been successfully locked.
|
|
54
|
+
#
|
|
55
|
+
# @param [Hash] opts for the locking mechanism
|
|
56
|
+
# @option opts [Fixnum] :timeout The number of seconds until the lock is considered "expired" - defaults to the {ClassMethods#lock_timeout}
|
|
57
|
+
# @option opts [Boolean] :wait If the document is currently locked, wait until the lock expires and try again
|
|
58
|
+
# @return [void]
|
|
59
|
+
def with_lock opts={}, &block
|
|
60
|
+
self.lock opts
|
|
61
|
+
begin
|
|
62
|
+
yield
|
|
63
|
+
ensure
|
|
64
|
+
self.unlock
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
protected
|
|
70
|
+
|
|
71
|
+
def lock opts={}
|
|
72
|
+
coll = self.class.collection
|
|
73
|
+
time = Time.now
|
|
74
|
+
timeout = opts[:timeout] || self.class.lock_timeout
|
|
75
|
+
expiration = time + timeout
|
|
76
|
+
|
|
77
|
+
# lock the document atomically in the DB without persisting entire doc
|
|
78
|
+
record = coll.find_and_modify(
|
|
79
|
+
:query => {
|
|
80
|
+
:_id => self.id,
|
|
81
|
+
'$or' => [
|
|
82
|
+
# not locked
|
|
83
|
+
{:locked_until => nil},
|
|
84
|
+
# expired
|
|
85
|
+
{:locked_until => {'$lte' => time}}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
:update => {
|
|
89
|
+
'$set' => {
|
|
90
|
+
:locked_at => time,
|
|
91
|
+
:locked_until => expiration
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if record
|
|
97
|
+
# lock successful
|
|
98
|
+
self.locked_at = time
|
|
99
|
+
self.locked_until = expiration
|
|
100
|
+
else
|
|
101
|
+
# couldn't grab lock
|
|
102
|
+
|
|
103
|
+
existing_query = {
|
|
104
|
+
:_id => self.id,
|
|
105
|
+
:locked_until => {'$exists' => true}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if opts[:wait] && existing = coll.find(existing_query, :limit => 1).first
|
|
109
|
+
# doc is locked - wait until it expires
|
|
110
|
+
wait_time = existing.locked_until - Time.now
|
|
111
|
+
sleep wait_time if wait_time > 0
|
|
112
|
+
|
|
113
|
+
# only wait once
|
|
114
|
+
opts.dup
|
|
115
|
+
opts.delete :wait
|
|
116
|
+
|
|
117
|
+
# retry lock grab
|
|
118
|
+
self.lock opts
|
|
119
|
+
else
|
|
120
|
+
raise LockError.new("could not get lock")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def unlock
|
|
126
|
+
# unlock the document in the DB without persisting entire doc
|
|
127
|
+
self.class.collection.update({:_id => self.id}, {
|
|
128
|
+
'$set' => {
|
|
129
|
+
:locked_at => nil,
|
|
130
|
+
:locked_until => nil,
|
|
131
|
+
}
|
|
132
|
+
}, {:safe => true})
|
|
133
|
+
|
|
134
|
+
self.locked_at = nil
|
|
135
|
+
self.locked_until = nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Error thrown if document could not be successfully locked.
|
|
140
|
+
class LockError < Exception; end
|
|
141
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Generated by jeweler
|
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
|
+
# -*- encoding: utf-8 -*-
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |s|
|
|
7
|
+
s.name = "mongoid-locker"
|
|
8
|
+
s.version = "0.1.0"
|
|
9
|
+
|
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
|
+
s.authors = ["Aidan Feldman"]
|
|
12
|
+
s.date = "2012-07-03"
|
|
13
|
+
s.description = "Allows multiple processes to operate on individual documents in MongoDB while ensuring that only one can act at a time."
|
|
14
|
+
s.email = "aidan.feldman@gmail.com"
|
|
15
|
+
s.extra_rdoc_files = [
|
|
16
|
+
"LICENSE.txt",
|
|
17
|
+
"README.md"
|
|
18
|
+
]
|
|
19
|
+
s.files = [
|
|
20
|
+
".document",
|
|
21
|
+
".rspec",
|
|
22
|
+
".travis.yml",
|
|
23
|
+
"Gemfile",
|
|
24
|
+
"Guardfile",
|
|
25
|
+
"LICENSE.txt",
|
|
26
|
+
"README.md",
|
|
27
|
+
"Rakefile",
|
|
28
|
+
"VERSION",
|
|
29
|
+
"lib/mongoid-locker.rb",
|
|
30
|
+
"lib/mongoid/locker.rb",
|
|
31
|
+
"mongoid-locker.gemspec",
|
|
32
|
+
"spec/database.yml",
|
|
33
|
+
"spec/mongoid-locker_spec.rb",
|
|
34
|
+
"spec/spec_helper.rb"
|
|
35
|
+
]
|
|
36
|
+
s.homepage = "http://github.com/afeld/mongoid-locker"
|
|
37
|
+
s.licenses = ["MIT"]
|
|
38
|
+
s.require_paths = ["lib"]
|
|
39
|
+
s.rubygems_version = "1.8.24"
|
|
40
|
+
s.summary = "Document-level locking for MongoDB via Mongoid"
|
|
41
|
+
|
|
42
|
+
if s.respond_to? :specification_version then
|
|
43
|
+
s.specification_version = 3
|
|
44
|
+
|
|
45
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
46
|
+
s.add_runtime_dependency(%q<mongoid>, ["~> 2.4"])
|
|
47
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.8"])
|
|
48
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.1"])
|
|
49
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.8"])
|
|
50
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
|
51
|
+
s.add_development_dependency(%q<bson_ext>, [">= 0"])
|
|
52
|
+
else
|
|
53
|
+
s.add_dependency(%q<mongoid>, ["~> 2.4"])
|
|
54
|
+
s.add_dependency(%q<rspec>, ["~> 2.8"])
|
|
55
|
+
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
|
56
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
|
57
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
|
58
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
s.add_dependency(%q<mongoid>, ["~> 2.4"])
|
|
62
|
+
s.add_dependency(%q<rspec>, ["~> 2.8"])
|
|
63
|
+
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
|
64
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8"])
|
|
65
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
|
66
|
+
s.add_dependency(%q<bson_ext>, [">= 0"])
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
data/spec/database.yml
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
|
2
|
+
|
|
3
|
+
describe Mongoid::Locker do
|
|
4
|
+
def remove_class klass
|
|
5
|
+
Object.send :remove_const, klass.to_s.to_sym
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
# recreate the class for each spec
|
|
10
|
+
class User
|
|
11
|
+
include Mongoid::Document
|
|
12
|
+
include Mongoid::Locker
|
|
13
|
+
|
|
14
|
+
field :account_balance, :type => Integer # easier to test than Float
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
@user = User.create!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
after do
|
|
21
|
+
User.delete_all
|
|
22
|
+
remove_class User
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
describe "#locked?" do
|
|
27
|
+
it "shouldn't be locked when created" do
|
|
28
|
+
@user.should_not be_locked
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "should respect the expiration" do
|
|
32
|
+
User.timeout_lock_after 1
|
|
33
|
+
|
|
34
|
+
@user.with_lock do
|
|
35
|
+
sleep 2
|
|
36
|
+
@user.should_not be_locked
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
describe "#with_lock" do
|
|
42
|
+
it "should lock and unlock the user" do
|
|
43
|
+
@user.with_lock do
|
|
44
|
+
@user.should be_locked
|
|
45
|
+
User.first.should be_locked
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
@user.should_not be_locked
|
|
49
|
+
@user.reload.should_not be_locked
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "shouldn't save the full document" do
|
|
53
|
+
@user.with_lock do
|
|
54
|
+
@user.account_balance = 10
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@user.account_balance.should eq(10)
|
|
58
|
+
User.first.account_balance.should be_nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should handle errors gracefully" do
|
|
62
|
+
expect {
|
|
63
|
+
@user.with_lock do
|
|
64
|
+
raise "booyah!"
|
|
65
|
+
end
|
|
66
|
+
}.to raise_error
|
|
67
|
+
|
|
68
|
+
@user.reload.should_not be_locked
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "should complain if trying to lock locked doc" do
|
|
72
|
+
@user.with_lock do
|
|
73
|
+
user_dup = User.first
|
|
74
|
+
|
|
75
|
+
expect {
|
|
76
|
+
user_dup.with_lock do
|
|
77
|
+
fail "shouldn't get the lock"
|
|
78
|
+
end
|
|
79
|
+
}.to raise_error(Mongoid::LockError)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should wait until the lock times out, if desired" do
|
|
84
|
+
User.timeout_lock_after 1
|
|
85
|
+
|
|
86
|
+
@user.with_lock do
|
|
87
|
+
user_dup = User.first
|
|
88
|
+
|
|
89
|
+
user_dup.with_lock :wait => true do
|
|
90
|
+
user_dup.account_balance = 10
|
|
91
|
+
user_dup.save!
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@user.reload.account_balance.should eq(10)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it "should override the default timeout" do
|
|
99
|
+
User.timeout_lock_after 1
|
|
100
|
+
|
|
101
|
+
expiration = (Time.now + 3).to_i
|
|
102
|
+
@user.with_lock :timeout => 3 do
|
|
103
|
+
@user.locked_until.to_i.should eq(expiration)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
describe ".timeout_lock_after" do
|
|
109
|
+
it "should ignore the lock if it has timed out" do
|
|
110
|
+
User.timeout_lock_after 1
|
|
111
|
+
|
|
112
|
+
@user.with_lock do
|
|
113
|
+
user_dup = User.first
|
|
114
|
+
sleep 2
|
|
115
|
+
|
|
116
|
+
user_dup.with_lock do
|
|
117
|
+
user_dup.account_balance = 10
|
|
118
|
+
user_dup.save!
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
@user.reload.account_balance.should eq(10)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "should be independent for different classes" do
|
|
126
|
+
class Account
|
|
127
|
+
include Mongoid::Document
|
|
128
|
+
include Mongoid::Locker
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
User.timeout_lock_after 1
|
|
132
|
+
Account.timeout_lock_after 2
|
|
133
|
+
|
|
134
|
+
User.lock_timeout.should eq(1)
|
|
135
|
+
|
|
136
|
+
remove_class Account
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
describe ".locked" do
|
|
141
|
+
it "should return the locked documents" do
|
|
142
|
+
user2 = User.create!
|
|
143
|
+
|
|
144
|
+
@user.with_lock do
|
|
145
|
+
User.locked.to_a.should eq([@user])
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe ".unlocked" do
|
|
151
|
+
it "should return the unlocked documents" do
|
|
152
|
+
user2 = User.create!
|
|
153
|
+
|
|
154
|
+
@user.with_lock do
|
|
155
|
+
User.unlocked.to_a.should eq([user2])
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require 'mongoid-locker'
|
|
5
|
+
|
|
6
|
+
ENV['RACK_ENV'] = 'test'
|
|
7
|
+
|
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
|
9
|
+
# in ./support/ and its subdirectories.
|
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
11
|
+
|
|
12
|
+
RSpec.configure do |config|
|
|
13
|
+
Mongoid.load! File.join(File.dirname(__FILE__), 'database.yml')
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mongoid-locker
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Aidan Feldman
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-07-03 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: mongoid
|
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.4'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
none: false
|
|
26
|
+
requirements:
|
|
27
|
+
- - ~>
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '2.4'
|
|
30
|
+
- !ruby/object:Gem::Dependency
|
|
31
|
+
name: rspec
|
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
|
33
|
+
none: false
|
|
34
|
+
requirements:
|
|
35
|
+
- - ~>
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '2.8'
|
|
38
|
+
type: :development
|
|
39
|
+
prerelease: false
|
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ~>
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.8'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: bundler
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ~>
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.1'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
none: false
|
|
58
|
+
requirements:
|
|
59
|
+
- - ~>
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.1'
|
|
62
|
+
- !ruby/object:Gem::Dependency
|
|
63
|
+
name: jeweler
|
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
|
65
|
+
none: false
|
|
66
|
+
requirements:
|
|
67
|
+
- - ~>
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '1.8'
|
|
70
|
+
type: :development
|
|
71
|
+
prerelease: false
|
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
73
|
+
none: false
|
|
74
|
+
requirements:
|
|
75
|
+
- - ~>
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '1.8'
|
|
78
|
+
- !ruby/object:Gem::Dependency
|
|
79
|
+
name: guard-rspec
|
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
|
81
|
+
none: false
|
|
82
|
+
requirements:
|
|
83
|
+
- - ! '>='
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '0'
|
|
86
|
+
type: :development
|
|
87
|
+
prerelease: false
|
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
89
|
+
none: false
|
|
90
|
+
requirements:
|
|
91
|
+
- - ! '>='
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: bson_ext
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
none: false
|
|
106
|
+
requirements:
|
|
107
|
+
- - ! '>='
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
description: Allows multiple processes to operate on individual documents in MongoDB
|
|
111
|
+
while ensuring that only one can act at a time.
|
|
112
|
+
email: aidan.feldman@gmail.com
|
|
113
|
+
executables: []
|
|
114
|
+
extensions: []
|
|
115
|
+
extra_rdoc_files:
|
|
116
|
+
- LICENSE.txt
|
|
117
|
+
- README.md
|
|
118
|
+
files:
|
|
119
|
+
- .document
|
|
120
|
+
- .rspec
|
|
121
|
+
- .travis.yml
|
|
122
|
+
- Gemfile
|
|
123
|
+
- Guardfile
|
|
124
|
+
- LICENSE.txt
|
|
125
|
+
- README.md
|
|
126
|
+
- Rakefile
|
|
127
|
+
- VERSION
|
|
128
|
+
- lib/mongoid-locker.rb
|
|
129
|
+
- lib/mongoid/locker.rb
|
|
130
|
+
- mongoid-locker.gemspec
|
|
131
|
+
- spec/database.yml
|
|
132
|
+
- spec/mongoid-locker_spec.rb
|
|
133
|
+
- spec/spec_helper.rb
|
|
134
|
+
homepage: http://github.com/afeld/mongoid-locker
|
|
135
|
+
licenses:
|
|
136
|
+
- MIT
|
|
137
|
+
post_install_message:
|
|
138
|
+
rdoc_options: []
|
|
139
|
+
require_paths:
|
|
140
|
+
- lib
|
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
|
+
none: false
|
|
143
|
+
requirements:
|
|
144
|
+
- - ! '>='
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: '0'
|
|
147
|
+
segments:
|
|
148
|
+
- 0
|
|
149
|
+
hash: 2896270749272846608
|
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
|
+
none: false
|
|
152
|
+
requirements:
|
|
153
|
+
- - ! '>='
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
requirements: []
|
|
157
|
+
rubyforge_project:
|
|
158
|
+
rubygems_version: 1.8.24
|
|
159
|
+
signing_key:
|
|
160
|
+
specification_version: 3
|
|
161
|
+
summary: Document-level locking for MongoDB via Mongoid
|
|
162
|
+
test_files: []
|