mm-optimistic_locking 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ mm-optimistic_locking
2
+ =====================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/highgroove/mm-optimistic_locking.png)](http://travis-ci.org/highgroove/mm-optimistic_locking)
5
+
6
+ mm-optimistic_locking implements optimistic locking in mongo_mapper.
7
+
8
+ Optimistic Locking?
9
+ -------------------
10
+
11
+ When attempting to save a record, optimistic locking verifies that the
12
+ record has not been modified by another process since it was earlier loaded
13
+ into memory. If it has been modified and resaved since that time, an error is
14
+ raised and the record must be reloaded and resaved again.
15
+
16
+ Optimistic locking is appropriate when the chance of a conflict is low
17
+ and records can easily recover when a conflict does occur (e.g., simply
18
+ by retrying the operations or by asking an end user to manually resolve the
19
+ conflict).
20
+
21
+ Before using mm-optimistic_locking, you should first consider if your use case
22
+ for locking can be handled by MongoDB's various [atomic operations](http://www.mongodb.org/display/DOCS/Atomic+Operations).
23
+ It is possible that you do not need this type of locking at all.
24
+
25
+ Quickstart
26
+ ----------
27
+
28
+ If using Bundler (e.g., with Rails 3), add the gem to your project's
29
+ Gemfile:
30
+
31
+ ``` ruby
32
+ gem 'mm-optimistic_locking'
33
+ ```
34
+
35
+ And run `bundle install`.
36
+
37
+ Next, include `MongoMapper::Plugins::OptimisticLocking` in each model
38
+ that needs optimistic locking functionality.
39
+
40
+ ``` ruby
41
+ class BlogPost
42
+ include MongoMapper::Document
43
+
44
+ # Add the following line to each model that needs optimistic locking
45
+ plugin MongoMapper::Plugins::OptimisticLocking
46
+ end
47
+ ```
48
+
49
+ Finally, make sure to rescue from `MongoMapper::StaleDocumentError`
50
+ any time you save a model with optimistic locking functionality:
51
+
52
+ ``` ruby
53
+ begin
54
+ # .. making modifications to blog_post ..
55
+ blog_post.save
56
+ rescue MongoMapper::StaleDocumentError
57
+ # Reload, remodify, and resave
58
+ # e.g.:
59
+ blog_post.reload
60
+ retry
61
+ end
62
+ ```
63
+
64
+ Related Projects
65
+ ----------------
66
+
67
+ * [mongo_mapper](http://mongomapper.com/)
68
+
69
+ Contributing
70
+ ------------
71
+
72
+ * Fork the repository on [GitHub](https://github.com/highgroove/mm-optimistic_locking)
73
+ * Create a [feature branch](http://nvie.com/posts/a-successful-git-branching-model/) (a.k.a., create a branch named my-awesome-feature)
74
+ * Write the new feature (please also add specs)
75
+ * Make sure all specs pass: `rake spec`
76
+ * Submit a pull request that only includes your feature branch
77
+
78
+ MIT License
79
+ -----------
80
+ Copyright (c) 2011 Highgroove Studios
81
+
82
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
87
+
88
+ ![Readme.meme](http://i.imgur.com/W3Gob.png)
@@ -1,17 +1,8 @@
1
1
  require 'mongo_mapper'
2
2
 
3
- module MongoMapper
4
- autoload :StaleDocumentError, "mongo_mapper/stale_document_error"
3
+ require File.expand_path("mongo_mapper/stale_document_error", File.dirname(__FILE__))
4
+ require File.expand_path("mongo_mapper/plugins/optimistic_locking", File.dirname(__FILE__))
5
5
 
6
- module Plugins
7
- autoload :OptimisticLocking, "mongo_mapper/plugins/optimistic_locking.rb"
8
- end
9
-
10
- module Plugins
11
- module Querying
12
- module InstanceMethods
13
- include MongoMapper::Plugins::OptimisticLocking::QueryingInterceptor
14
- end
15
- end
16
- end
6
+ MongoMapper::Plugins::Querying::InstanceMethods.class_eval do
7
+ include MongoMapper::Plugins::OptimisticLocking::QueryingInterceptor
17
8
  end
@@ -0,0 +1,15 @@
1
+ require 'active_support/concern'
2
+
3
+ require File.expand_path("optimistic_locking/querying_interceptor", File.dirname(__FILE__))
4
+
5
+ module MongoMapper
6
+ module Plugins
7
+ module OptimisticLocking
8
+ extend ActiveSupport::Concern
9
+
10
+ included do |base|
11
+ base.key :_lock_version, Integer, :default => 0
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,32 @@
1
+ module MongoMapper
2
+ module Plugins
3
+ module OptimisticLocking
4
+ module QueryingInterceptor
5
+ def self.included(base)
6
+ base.class_eval do
7
+ alias_method_chain :save_to_collection, :optimistic_locking
8
+ end
9
+ end
10
+
11
+ def save_to_collection_with_optimistic_locking(options = {})
12
+ if persisted? && keys.keys.include?("_lock_version")
13
+ current_lock_version = self._lock_version
14
+ begin
15
+ self._lock_version += 1
16
+ result = collection.update({:_id => self._id, :_lock_version => current_lock_version},
17
+ to_mongo,
18
+ :upsert => false, :safe => true)
19
+
20
+ raise MongoMapper::StaleDocumentError.new(self) unless result["updatedExisting"]
21
+ rescue
22
+ self._lock_version -= 1
23
+ raise
24
+ end
25
+ else
26
+ save_to_collection_without_optimistic_locking(options)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,10 +1,8 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "mm-optimistic_locking"
4
2
 
5
3
  Gem::Specification.new do |s|
6
4
  s.name = "mm-optimistic_locking"
7
- s.version = MongoMapper::Plugins::OptimisticLocking::VERSION
5
+ s.version = "0.0.3"
8
6
  s.authors = ["Andy Lindeman"]
9
7
  s.email = ["andy@highgroove.com"]
10
8
  s.homepage = "http://github.com/highgroove/mm-optimistic_locking"
@@ -15,12 +13,17 @@ Gem::Specification.new do |s|
15
13
  ".gitignore",
16
14
  "Gemfile",
17
15
  "Rakefile",
16
+ "README.md",
18
17
  "lib/mm-optimistic_locking.rb",
19
18
  "lib/mongo_mapper/stale_document_error.rb",
20
- "mm-optimistic_locking.gemspec",
19
+ "lib/mongo_mapper/plugins/optimistic_locking.rb",
20
+ "lib/mongo_mapper/plugins/optimistic_locking/querying_interceptor.rb",
21
+ "mm-optimistic_locking.gemspec"
21
22
  ]
22
23
 
23
24
  s.test_files = [
25
+ "spec/spec_helper.rb",
26
+ "spec/integration/optimistic_locking_spec.rb"
24
27
  ]
25
28
 
26
29
  s.executables = [
@@ -28,7 +31,7 @@ Gem::Specification.new do |s|
28
31
 
29
32
  s.require_paths = ["lib"]
30
33
 
31
- s.add_dependency 'mongo_mapper', '~>0.9.0'
34
+ s.add_dependency 'mongo_mapper', '>=0.9.0'
32
35
  s.add_dependency 'activesupport', '~>3.0'
33
36
 
34
37
  s.add_development_dependency 'rake', '~>0.8.7'
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe "optimistic locking plugin" do
4
+ describe "models with plugin" do
5
+ subject do
6
+ Class.new do
7
+ include MongoMapper::Document
8
+ plugin MongoMapper::Plugins::OptimisticLocking
9
+
10
+ key :foo_text, String
11
+ end.new
12
+ end
13
+
14
+ context "new records" do
15
+ it "saves the record normally" do
16
+ subject.foo_text = "foo bar baz"
17
+ subject.save
18
+
19
+ subject.reload.foo_text.should eql "foo bar baz"
20
+ end
21
+ end
22
+
23
+ context "previously persisted records" do
24
+ before do
25
+ subject.save!
26
+ end
27
+
28
+ it "saves the record normally if no conflicts exist" do
29
+ subject.foo_text = "foo bar baz"
30
+ subject.save
31
+
32
+ subject.reload.foo_text.should eql "foo bar baz"
33
+ end
34
+
35
+ it "raises a StaleDocumentError if a conflict is detected" do
36
+ dupped_subject = subject.class.find(subject.id)
37
+ dupped_subject.foo_text = "baz bar foo"
38
+ dupped_subject.save
39
+
40
+ expect {
41
+ subject.foo_text = "foo bar baz"
42
+ subject.save
43
+ }.to raise_error(MongoMapper::StaleDocumentError)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "models without plugin" do
49
+ subject do
50
+ Class.new do
51
+ include MongoMapper::Document
52
+
53
+ key :foo_text, String
54
+ end.new
55
+ end
56
+
57
+ it "allows records to interfere with one another" do
58
+ subject.save
59
+
60
+ dupped_subject = subject.class.find(subject.id)
61
+ dupped_subject.foo_text = "baz bar foo"
62
+ dupped_subject.save
63
+
64
+ expect {
65
+ subject.foo_text = "foo bar baz"
66
+ subject.save
67
+ }.to_not raise_error
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec'
5
+ require 'database_cleaner'
6
+
7
+ begin
8
+ require 'ruby-debug'
9
+ rescue LoadError
10
+ end
11
+
12
+ require File.expand_path("../lib/mm-optimistic_locking", File.dirname(__FILE__))
13
+
14
+ MongoMapper.connection = Mongo::Connection.new('localhost', 27017)
15
+ MongoMapper.database = "mm-optimistic_locking_test"
16
+
17
+ RSpec.configure do |c|
18
+ c.before(:suite) do
19
+ DatabaseCleaner.strategy = :truncation
20
+ DatabaseCleaner.clean_with :truncation
21
+ end
22
+
23
+ c.before(:each) do
24
+ DatabaseCleaner.start
25
+ end
26
+
27
+ c.after(:each) do
28
+ DatabaseCleaner.clean
29
+ end
30
+ end
metadata CHANGED
@@ -1,116 +1,121 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: mm-optimistic_locking
3
- version: !ruby/object:Gem::Version
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
4
5
  prerelease:
5
- version: 0.0.1
6
6
  platform: ruby
7
- authors:
7
+ authors:
8
8
  - Andy Lindeman
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
-
13
- date: 2011-06-27 00:00:00 -04:00
14
- default_executable:
15
- dependencies:
16
- - !ruby/object:Gem::Dependency
12
+ date: 2011-09-13 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
17
15
  name: mongo_mapper
18
- prerelease: false
19
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &70335886501060 !ruby/object:Gem::Requirement
20
17
  none: false
21
- requirements:
22
- - - ~>
23
- - !ruby/object:Gem::Version
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
24
21
  version: 0.9.0
25
22
  type: :runtime
26
- version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: activesupport
29
23
  prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: *70335886501060
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70335886500420 !ruby/object:Gem::Requirement
31
28
  none: false
32
- requirements:
29
+ requirements:
33
30
  - - ~>
34
- - !ruby/object:Gem::Version
35
- version: "3.0"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
36
33
  type: :runtime
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: rake
40
34
  prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
35
+ version_requirements: *70335886500420
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70335886499740 !ruby/object:Gem::Requirement
42
39
  none: false
43
- requirements:
40
+ requirements:
44
41
  - - ~>
45
- - !ruby/object:Gem::Version
42
+ - !ruby/object:Gem::Version
46
43
  version: 0.8.7
47
44
  type: :development
48
- version_requirements: *id003
49
- - !ruby/object:Gem::Dependency
50
- name: rspec
51
45
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
46
+ version_requirements: *70335886499740
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70335886498920 !ruby/object:Gem::Requirement
53
50
  none: false
54
- requirements:
51
+ requirements:
55
52
  - - ~>
56
- - !ruby/object:Gem::Version
53
+ - !ruby/object:Gem::Version
57
54
  version: 2.6.0
58
55
  type: :development
59
- version_requirements: *id004
60
- - !ruby/object:Gem::Dependency
61
- name: database_cleaner
62
56
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
57
+ version_requirements: *70335886498920
58
+ - !ruby/object:Gem::Dependency
59
+ name: database_cleaner
60
+ requirement: &70335886498220 !ruby/object:Gem::Requirement
64
61
  none: false
65
- requirements:
62
+ requirements:
66
63
  - - ~>
67
- - !ruby/object:Gem::Version
64
+ - !ruby/object:Gem::Version
68
65
  version: 0.6.7
69
66
  type: :development
70
- version_requirements: *id005
71
- description: Before a record is saved, mm-optimistic_locking will check if it has been modified by another process. If so, a StaleDocumentError will be raised. The object can be reloaded and resaved after the conflict has been resolved.
72
- email:
67
+ prerelease: false
68
+ version_requirements: *70335886498220
69
+ description: Before a record is saved, mm-optimistic_locking will check if it has
70
+ been modified by another process. If so, a StaleDocumentError will be raised. The
71
+ object can be reloaded and resaved after the conflict has been resolved.
72
+ email:
73
73
  - andy@highgroove.com
74
74
  executables: []
75
-
76
75
  extensions: []
77
-
78
76
  extra_rdoc_files: []
79
-
80
- files:
77
+ files:
81
78
  - .gitignore
82
79
  - Gemfile
83
80
  - Rakefile
81
+ - README.md
84
82
  - lib/mm-optimistic_locking.rb
85
83
  - lib/mongo_mapper/stale_document_error.rb
84
+ - lib/mongo_mapper/plugins/optimistic_locking.rb
85
+ - lib/mongo_mapper/plugins/optimistic_locking/querying_interceptor.rb
86
86
  - mm-optimistic_locking.gemspec
87
- has_rdoc: true
87
+ - spec/spec_helper.rb
88
+ - spec/integration/optimistic_locking_spec.rb
88
89
  homepage: http://github.com/highgroove/mm-optimistic_locking
89
90
  licenses: []
90
-
91
91
  post_install_message:
92
92
  rdoc_options: []
93
-
94
- require_paths:
93
+ require_paths:
95
94
  - lib
96
- required_ruby_version: !ruby/object:Gem::Requirement
95
+ required_ruby_version: !ruby/object:Gem::Requirement
97
96
  none: false
98
- requirements:
99
- - - ">="
100
- - !ruby/object:Gem::Version
101
- version: "0"
102
- required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ segments:
102
+ - 0
103
+ hash: -3576524229858100918
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
105
  none: false
104
- requirements:
105
- - - ">="
106
- - !ruby/object:Gem::Version
107
- version: "0"
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ segments:
111
+ - 0
112
+ hash: -3576524229858100918
108
113
  requirements: []
109
-
110
114
  rubyforge_project:
111
- rubygems_version: 1.6.2
115
+ rubygems_version: 1.8.6
112
116
  signing_key:
113
117
  specification_version: 3
114
118
  summary: Implements optimistic locking (similar to ActiveRecord) for MongoMapper
115
- test_files: []
116
-
119
+ test_files:
120
+ - spec/spec_helper.rb
121
+ - spec/integration/optimistic_locking_spec.rb