mongoid_optimistic_locking 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
3
  - 1.9.2
5
4
  - 1.9.3
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in mongoid_optimistic_locking.gemspec
4
3
  gemspec
data/README.md CHANGED
@@ -6,9 +6,9 @@ The gem is an addon over [Mongoid ODM](http://mongoid.org/) and is based on [Act
6
6
 
7
7
  ## Compatibility
8
8
 
9
- So far it works with the Rails 3 and Mongoid 2.4.
9
+ Works with *Mongoid 3*.
10
10
 
11
- Created branch for edge *Mongoid 3*.
11
+ For Mongoid 2 use version 0.0.2
12
12
 
13
13
  ## Rails 3 Installation
14
14
 
@@ -23,7 +23,6 @@ To use it, all you have to do is add include `Mongoid::OptimisticLocking`:
23
23
  class Post
24
24
  include Mongoid::Document
25
25
  include Mongoid::OptimisticLocking
26
-
27
26
  field :text
28
27
  end
29
28
 
@@ -47,6 +46,28 @@ For example:
47
46
 
48
47
  That's it!
49
48
 
49
+ ## Embedded Document Caveats
50
+
51
+ While `Mongoid::OptimisticLocking` can be used to some degree within embedded documents, there are certain limitations due to Mongoid's document embedding callback structure. Consider the following example:
52
+
53
+ class Post
54
+ include Mongoid::Document
55
+ field :text
56
+ embeds_many :comments
57
+ end
58
+
59
+ class Comment
60
+ include Mongoid::Document
61
+ include Mongoid::OptimisticLocking
62
+ embedded_in :post
63
+ field :text
64
+ end
65
+
66
+ post = Post.new
67
+ comment = post.comments.build(:text => 'hello')
68
+ comment.save # will use optimistic locking checks
69
+ post.save # will not use optimistic locking checks
70
+
50
71
  ## Open sourced by
51
72
 
52
73
  [Boxee](http://www.boxee.tv)
@@ -55,3 +76,5 @@ That's it!
55
76
  [Mongo Developer FAQ - How do I do transactions/locking?](http://docs.mongodb.org/manual/faq/developers/#how-do-i-do-transactions-and-locking-in-mongodb)
56
77
 
57
78
  [Mongo Atomic Operations - "Update if Current"](http://www.mongodb.org/display/DOCS/Atomic+Operations#AtomicOperations-%22UpdateifCurrent%22)
79
+
80
+ [Presentation from "Startup Day" on Mongoid Optimistic Locking](https://speakerdeck.com/u/burgalon/p/mongoid-optimistic-locking)
@@ -2,19 +2,20 @@ module Mongoid
2
2
  module OptimisticLocking
3
3
  module Operations
4
4
 
5
- def insert(*args)
5
+ def insert(options = {})
6
6
  return super unless optimistic_locking?
7
7
  increment_lock_version do
8
8
  super
9
9
  end
10
10
  end
11
11
 
12
- def update(*args)
12
+ def update(options = {})
13
13
  return super unless optimistic_locking?
14
14
  set_lock_version_for_selector do
15
15
  increment_lock_version do
16
16
  result = super
17
- unless Mongoid.database.command({:getlasterror => 1})['updatedExisting']
17
+ getlasterror = mongo_session.command({:getlasterror => 1})
18
+ if result && !getlasterror['updatedExisting']
18
19
  raise Mongoid::Errors::StaleDocument.new('update', self)
19
20
  end
20
21
  result
@@ -22,15 +23,16 @@ module Mongoid
22
23
  end
23
24
  end
24
25
 
25
- def remove(*args)
26
- return super unless optimistic_locking?
27
- set_lock_version_for_selector do
28
- result = super
29
- unless Mongoid.database.command({:getlasterror => 1})['updatedExisting']
30
- raise Mongoid::Errors::StaleDocument.new('destroy', self)
31
- end
32
- result
26
+ def remove(options = {})
27
+ return super unless optimistic_locking? && persisted?
28
+ # we need to just see if the document exists and got updated with
29
+ # a higher lock version
30
+ existing = _reload # get the current root or embedded document
31
+ if existing.present? && existing['_lock_version'] &&
32
+ existing['_lock_version'].to_i > _lock_version.to_i
33
+ raise Mongoid::Errors::StaleDocument.new('destroy', self)
33
34
  end
35
+ super
34
36
  end
35
37
 
36
38
  def atomic_selector
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module OptimisticLocking
3
- VERSION = '0.0.2'
3
+ VERSION = '0.0.3'
4
4
  end
5
5
  end
@@ -11,10 +11,9 @@ Gem::Specification.new do |s|
11
11
  s.summary = %q{Allows optimisitic locking for Mongoid models}
12
12
  s.description = %q{Allows optimisitic locking for Mongoid models. See https://github.com/burgalon/mongoid_optimistic_locking}
13
13
 
14
- s.add_dependency 'mongoid', '~> 2.4'
14
+ s.add_dependency 'mongoid', '>= 3.0.0rc'
15
15
  s.add_development_dependency 'rake', '~> 0.9.0'
16
16
  s.add_development_dependency 'rspec', '~> 2.6'
17
- s.add_development_dependency 'bson_ext', '~> 1.5'
18
17
 
19
18
  s.rubyforge_project = "mongoid_optimistic_locking"
20
19
 
@@ -2,106 +2,296 @@ require 'spec_helper'
2
2
 
3
3
  describe Mongoid::OptimisticLocking do
4
4
 
5
- context 'without optimistic locking' do
5
+ COLLECTIONS = %w(Company Person Car State Flag City)
6
6
 
7
- let(:company_class) do
8
- Class.new do
9
- include Mongoid::Document
10
- self.collection_name = 'companies'
11
- field :name
12
- end
7
+ before(:all) do
8
+
9
+ class Company
10
+ include Mongoid::Document
11
+ field :name
12
+ has_many :people, :class_name => 'Person', :dependent => :destroy
13
+ end
14
+
15
+ class Person
16
+ include Mongoid::Document
17
+ include Mongoid::OptimisticLocking
18
+ field :name
19
+ has_many :cars, :dependent => :destroy
13
20
  end
14
21
 
15
- it 'should allow collisions' do
16
- c1 = company_class.create!(:name => 'Acme')
17
- c2 = company_class.find(c1.id)
22
+ class Car
23
+ include Mongoid::Document
24
+ include Mongoid::OptimisticLocking
25
+ field :color
26
+ belongs_to :person
27
+ end
28
+
29
+ class State
30
+ include Mongoid::Document
31
+ field :name
32
+ embeds_one :flag, :inverse_of => :state
33
+ embeds_many :cities, :class_name => 'City', :inverse_of => :state
34
+ end
35
+
36
+ class Flag
37
+ include Mongoid::Document
38
+ include Mongoid::OptimisticLocking
39
+ embedded_in :state, :inverse_of => :flag
40
+ field :color
41
+ end
42
+
43
+ class City
44
+ include Mongoid::Document
45
+ include Mongoid::OptimisticLocking
46
+ embedded_in :state, :inverse_of => :cities
47
+ field :name
48
+ end
49
+
50
+ end
51
+
52
+ after(:all) { COLLECTIONS.each { |n| Object.send(:remove_const, n) } }
53
+ before { (COLLECTIONS - %w(Flag City)).each { |n| n.constantize.delete_all } }
54
+
55
+ context 'without optimistic locking' do
56
+
57
+ let!(:c1) { Company.create!(:name => 'Acme') }
58
+ let!(:c2) { Company.find(c1.id) }
59
+
60
+ it 'should allow collisions when saving' do
18
61
  c1.name = 'Biz'
19
- c1.save.should == true
62
+ c1.save.should be_true
20
63
  c2.name = 'Baz'
21
- c2.save.should == true
64
+ c2.save.should be_true
65
+ end
66
+
67
+ it 'should allow collisions when destroying' do
68
+ c1.destroy.should be_true
69
+ c2.destroy.should be_true
22
70
  end
23
71
 
24
72
  end
25
73
 
26
- context 'three instances of the same document' do
74
+ context 'optimistic locking on a document' do
27
75
 
28
- let(:person_class) do
29
- Class.new do
30
- include Mongoid::Document
31
- include Mongoid::OptimisticLocking
32
- self.collection_name = 'people'
33
- field :name
34
- end
76
+ let(:person) { Person.create!(:name => 'Bob') }
77
+
78
+ it 'should allow creating' do
79
+ person._lock_version.should == 1
80
+ person.save.should be_true
81
+ person._lock_version.should == 2
82
+ Person.count.should == 1
83
+ end
84
+
85
+ it 'should allow updating' do
86
+ person.name = 'Chris'
87
+ person.save.should be_true
88
+ person._lock_version.should == 2
89
+ end
90
+
91
+ it 'should allow destroying after create' do
92
+ person.destroy.should be_true
93
+ person.should be_frozen
94
+ person._lock_version.should == 1
95
+ Person.count.should == 0
35
96
  end
36
97
 
37
- before do
38
- @p1 = person_class.create!(:name => 'Bob')
39
- @p2 = person_class.find(@p1.id)
40
- @p3 = person_class.find(@p1.id)
98
+ it 'should allow destroying from find' do
99
+ another = Person.find(person.id)
100
+ another.destroy.should be_true
101
+ another.should be_frozen
102
+ another._lock_version.should == 1
103
+ Person.count.should == 0
41
104
  end
42
105
 
43
- context 'after updating the first' do
106
+ context 'after updating the first of 3 instances of the same document' do
44
107
 
45
108
  before do
109
+ @p1 = person
110
+ @p2 = Person.find(@p1.id)
111
+ @p3 = Person.find(@p1.id)
46
112
  @p1.name = 'Michael'
47
- @p1.save.should == true
113
+ @p1.save.should be_true
48
114
  end
49
115
 
50
- it 'should fail when updating the second' do
116
+ it 'should raise an exception when updating the second' do
51
117
  expect {
52
118
  @p2.name = 'George'
53
119
  @p2.save
54
120
  }.to raise_error(Mongoid::Errors::StaleDocument)
55
121
  end
56
-
122
+
57
123
  it 'should succeed when updating the second without locking' do
58
124
  @p2.name = 'George'
59
- @p2.unlocked.save.should == true
125
+ @p2.unlocked.save.should be_true
60
126
  end
61
-
127
+
62
128
  it 'should succeed when updating the second without locking, ' +
63
- 'then fail when updating the third' do
129
+ 'then raise an exception when updating the third' do
64
130
  @p2.name = 'George'
65
- @p2.unlocked.save.should == true
131
+ @p2.unlocked.save.should be_true
66
132
  expect {
67
133
  @p3.name = 'Sally'
68
134
  @p3.save
69
135
  }.to raise_error(Mongoid::Errors::StaleDocument)
70
136
  end
71
137
 
72
- it 'should fail when destroying the second' do
138
+ it 'should raise an exception when destroying the second' do
73
139
  expect {
74
140
  @p2.destroy
75
141
  }.to raise_error(Mongoid::Errors::StaleDocument)
142
+ Person.count.should == 1
76
143
  end
77
144
 
78
145
  it 'should succeed when destroying the second without locking' do
79
146
  @p2.unlocked.destroy
147
+ Person.count.should == 0
80
148
  end
81
149
 
82
150
  it 'should succeed when destroying the second without locking, ' +
83
- 'then fail when destroying the third' do
151
+ 'and succeed when destroying the third with locking' do
84
152
  @p2.unlocked.destroy
85
- expect {
86
- @p3.destroy
87
- }.to raise_error(Mongoid::Errors::StaleDocument)
153
+ Person.count.should == 0
154
+ @p3.destroy.should
88
155
  end
89
156
 
90
157
  end
91
158
 
159
+ it 'should destroy dependents with dependent destroy option' do
160
+ Car.count.should == 0
161
+ person.cars.create(:color => 'Red')
162
+ person.destroy
163
+ Car.count.should == 0
164
+ end
165
+
166
+ it 'should destroy dependents with dependent destroy option even when they are concurrently edited' do
167
+ car1 = person.cars.create(:color => 'Red')
168
+ car2 = Car.find(car1.id)
169
+ car2.update_attribute :color, 'Green'
170
+ person.reload # important
171
+ person.destroy
172
+ Person.count.should == 0
173
+ Car.count.should == 0
174
+ end
175
+
92
176
  it 'should give a deprecation warning for #save_optimistic!' do
93
177
  ::ActiveSupport::Deprecation.should_receive(:warn).once
94
- @p1.save_optimistic!
178
+ person.save_optimistic!
95
179
  end
96
180
 
97
- it 'should give a deprecation warning for including Mongoid::Lockable' do
98
- ::ActiveSupport::Deprecation.should_receive(:warn).once
99
- Class.new do
100
- include Mongoid::Document
101
- include Mongoid::Lockable
102
- end
181
+ end
182
+
183
+ context 'optimistic locking on an embeds_one document' do
184
+
185
+ let!(:state) { State.new(:name => 'California') }
186
+
187
+ it 'should be savable with no embeds' do
188
+ state.name = 'Nevada'
189
+ state.save.should be_true
190
+ end
191
+
192
+ it 'should be savable with a built embed' do
193
+ state.build_flag(:color => 'Red')
194
+ state.save.should be_true
195
+ end
196
+
197
+ it 'should allow creating the embed' do
198
+ state.create_flag(:color => 'Red')
199
+ state.flag.should be_persisted
200
+ end
201
+
202
+ it 'should not increment the lock version when saving the base' do
203
+ state.build_flag(:color => 'Red')
204
+ state.flag._lock_version.should == 0
205
+ state.save
206
+ state.flag._lock_version.should == 0
207
+ end
208
+
209
+ it 'should increment lock version when saving the embed' do
210
+ state.build_flag(:color => 'Red')
211
+ state.flag._lock_version.should == 0
212
+ state.flag.save
213
+ state.flag._lock_version.should == 1
214
+ end
215
+
216
+ it 'should not raise an exception on saving the base when another process updated it' do
217
+ flag1 = state.create_flag(:color => 'Red')
218
+ flag2 = State.find(state.id).flag
219
+ flag1.color = 'Green'
220
+ state.save # doesn't increment lock version
221
+ flag2.update_attribute(:color, 'Purple').should be_true
222
+ end
223
+
224
+ it 'should raise an exception on update when another process updated it' do
225
+ flag1 = state.create_flag(:color => 'Red')
226
+ flag2 = State.find(state.id).flag
227
+ flag1.update_attribute :color, 'Green'
228
+ expect {
229
+ flag2.update_attribute :color, 'Purple'
230
+ }.to raise_error(Mongoid::Errors::StaleDocument)
231
+ end
232
+
233
+ it 'should raise an exception on destroy when another process updated it' do
234
+ flag1 = state.create_flag(:color => 'Red')
235
+ flag2 = State.find(state.id).flag
236
+ flag1.update_attribute :color, 'Green'
237
+ expect {
238
+ flag2.destroy
239
+ }.to raise_error(Mongoid::Errors::StaleDocument)
240
+ end
241
+
242
+ end
243
+
244
+ context 'optimistic locking on an embeds_many document' do
245
+
246
+ let!(:state) { State.create!(:name => 'California') }
247
+
248
+ it 'should be savable with no embeds' do
249
+ state.name = 'Nevada'
250
+ state.save.should be_true
251
+ end
252
+
253
+ it 'should be savable with a built embed' do
254
+ state.cities.build(:name => 'Los Angeles')
255
+ state.save.should be_true
256
+ end
257
+
258
+ it 'should allow embedded build to save' do
259
+ city = state.cities.build(:name => 'Los Angeles')
260
+ city.save.should be_true
261
+ end
262
+
263
+ it 'should allow embedded create' do
264
+ city = state.cities.create(:name => 'Los Angeles')
265
+ city.should be_persisted
103
266
  end
104
267
 
268
+ it 'should not increment the lock version when saving the base' do
269
+ city = state.cities.build(:name => 'Los Angeles')
270
+ city._lock_version.should == 0
271
+ state.save
272
+ city._lock_version.should == 0
273
+ end
274
+
275
+ it 'should increment the lock version when building and saving the embed' do
276
+ city = state.cities.build(:name => 'Los Angeles')
277
+ city._lock_version.should == 0
278
+ city.save
279
+ city._lock_version.should == 1
280
+ end
281
+
282
+ it 'should increment the lock version when creating the embed' do
283
+ city = state.cities.create(:name => 'Los Angeles')
284
+ city._lock_version.should == 1
285
+ end
286
+
287
+ end
288
+
289
+ it 'should give a deprecation warning for including Mongoid::Lockable' do
290
+ ::ActiveSupport::Deprecation.should_receive(:warn).once
291
+ Class.new do
292
+ include Mongoid::Document
293
+ include Mongoid::Lockable
294
+ end
105
295
  end
106
296
 
107
297
  end
data/spec/spec_helper.rb CHANGED
@@ -4,8 +4,9 @@ Bundler.setup
4
4
 
5
5
  require 'mongoid'
6
6
 
7
+ # Set the database that the spec suite connects to.
7
8
  Mongoid.configure do |config|
8
- config.master = Mongo::Connection.new.db("mongoid_optimistic_locking_test")
9
+ config.connect_to('mongoid_optimistic_locking_test')
9
10
  end
10
11
 
11
12
  $LOAD_PATH.unshift(File.dirname(__FILE__))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid_optimistic_locking
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,22 +10,27 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-05-31 00:00:00.000000000 Z
13
+ date: 2013-03-05 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: mongoid
17
- requirement: &2161833600 !ruby/object:Gem::Requirement
17
+ requirement: !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
- - - ~>
20
+ - - ! '>='
21
21
  - !ruby/object:Gem::Version
22
- version: '2.4'
22
+ version: 3.0.0rc
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2161833600
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 3.0.0rc
26
31
  - !ruby/object:Gem::Dependency
27
32
  name: rake
28
- requirement: &2161832880 !ruby/object:Gem::Requirement
33
+ requirement: !ruby/object:Gem::Requirement
29
34
  none: false
30
35
  requirements:
31
36
  - - ~>
@@ -33,10 +38,15 @@ dependencies:
33
38
  version: 0.9.0
34
39
  type: :development
35
40
  prerelease: false
36
- version_requirements: *2161832880
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.9.0
37
47
  - !ruby/object:Gem::Dependency
38
48
  name: rspec
39
- requirement: &2161832180 !ruby/object:Gem::Requirement
49
+ requirement: !ruby/object:Gem::Requirement
40
50
  none: false
41
51
  requirements:
42
52
  - - ~>
@@ -44,18 +54,12 @@ dependencies:
44
54
  version: '2.6'
45
55
  type: :development
46
56
  prerelease: false
47
- version_requirements: *2161832180
48
- - !ruby/object:Gem::Dependency
49
- name: bson_ext
50
- requirement: &2161831560 !ruby/object:Gem::Requirement
57
+ version_requirements: !ruby/object:Gem::Requirement
51
58
  none: false
52
59
  requirements:
53
60
  - - ~>
54
61
  - !ruby/object:Gem::Version
55
- version: '1.5'
56
- type: :development
57
- prerelease: false
58
- version_requirements: *2161831560
62
+ version: '2.6'
59
63
  description: Allows optimisitic locking for Mongoid models. See https://github.com/burgalon/mongoid_optimistic_locking
60
64
  email:
61
65
  - burgalon@gmail.com
@@ -106,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
110
  version: '0'
107
111
  requirements: []
108
112
  rubyforge_project: mongoid_optimistic_locking
109
- rubygems_version: 1.8.10
113
+ rubygems_version: 1.8.24
110
114
  signing_key:
111
115
  specification_version: 3
112
116
  summary: Allows optimisitic locking for Mongoid models