mongomatic 0.7.1 → 0.7.2

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.
@@ -71,8 +71,6 @@ module Mongomatic
71
71
  do_callback(:after_drop)
72
72
  end
73
73
 
74
- private
75
-
76
74
  def do_callback(meth)
77
75
  return false unless respond_to?(meth, true)
78
76
  send(meth)
@@ -292,8 +290,6 @@ module Mongomatic
292
290
  @doc || {}
293
291
  end
294
292
 
295
- protected
296
-
297
293
  def hash_for_field(field, break_if_dne=false)
298
294
  parts = field.split(".")
299
295
  curr_hash = self.doc
@@ -313,5 +309,11 @@ module Mongomatic
313
309
  return false unless respond_to?(meth, true)
314
310
  send(meth)
315
311
  end
312
+
313
+ def transaction(key=nil, duration=5, &block)
314
+ raise Mongomatic::Exceptions::DocumentIsNew if new?
315
+ key ||= [self.class.name, self["_id"].to_s].join("-")
316
+ TransactionLock.start(key, duration, &block)
317
+ end
316
318
  end
317
319
  end
@@ -6,5 +6,7 @@ module Mongomatic
6
6
  class DocumentIsNew < Base; end
7
7
  class DocumentWasRemoved < Base; end
8
8
  class DocumentNotValid < Base; end
9
+
10
+ class CannotGetTransactionLock < Base; end
9
11
  end
10
12
  end
@@ -0,0 +1,35 @@
1
+ module Mongomatic
2
+
3
+ class TransactionLock < Base
4
+ def self.create_indexes
5
+ collection.create_index("key", :unique => true, :drop_dups => true)
6
+ collection.create_index("expire_at")
7
+ end
8
+
9
+ def self.start(key, duration, &block)
10
+ lock = new(:key => key, :expire_at => Time.now.utc + duration)
11
+
12
+ # we need to get a lock
13
+ begin
14
+ lock.insert!
15
+ rescue Mongo::OperationFailure => e
16
+ remove_stale_locks
17
+ if find_one(:key => key) == nil
18
+ return start(key, duration, &block)
19
+ end
20
+ raise Mongomatic::Exceptions::CannotGetTransactionLock
21
+ end
22
+
23
+ begin
24
+ block.call
25
+ ensure
26
+ lock.remove
27
+ end
28
+ end
29
+
30
+ def self.remove_stale_locks
31
+ collection.remove({:expire_at => {"$lte" => Time.now.utc}}, {:safe => true})
32
+ end
33
+ end
34
+
35
+ end
data/lib/mongomatic.rb CHANGED
@@ -45,3 +45,4 @@ require "#{File.dirname(__FILE__)}/mongomatic/active_model_compliancy"
45
45
  require "#{File.dirname(__FILE__)}/mongomatic/type_converters"
46
46
  require "#{File.dirname(__FILE__)}/mongomatic/typed_fields"
47
47
  require "#{File.dirname(__FILE__)}/mongomatic/base"
48
+ require "#{File.dirname(__FILE__)}/mongomatic/transaction_lock"
data/test/helper.rb CHANGED
@@ -122,5 +122,15 @@ class Rig < Mongomatic::Base
122
122
  typed_field "friends_rig_id", :type => :object_id, :cast => true
123
123
  end
124
124
 
125
-
126
-
125
+ class Clock < Mongomatic::Base
126
+ typed_field "ticks", :type => :fixnum, :cast => true
127
+
128
+ def tick!
129
+ transaction do
130
+ self.reload
131
+ self["ticks"] ||= 0
132
+ self["ticks"] += 1
133
+ self.update!
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,76 @@
1
+ require 'helper'
2
+ require 'minitest/autorun'
3
+
4
+ class TestTransactionLock < MiniTest::Unit::TestCase
5
+ def setup
6
+ Person.collection.drop
7
+ Mongomatic::TransactionLock.collection.drop
8
+ Mongomatic::TransactionLock.create_indexes
9
+ end
10
+
11
+ def test_will_rm_lock_when_complete
12
+ assert_equal 0, Mongomatic::TransactionLock.count
13
+ p1 = Person.new(:name => "Jordan")
14
+ p1.insert!
15
+ p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
16
+ assert_equal 0, Mongomatic::TransactionLock.count
17
+ p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
18
+ end
19
+
20
+ def test_will_raise_if_cant_get_lock
21
+ p1 = Person.new(:name => "Jordan")
22
+ p1.insert!
23
+
24
+ l = Mongomatic::TransactionLock.new(:key => "Person-#{p1["_id"]}", :expire_at => Time.now.utc + 1.day)
25
+ l.insert!
26
+
27
+ assert_raises(Mongomatic::Exceptions::CannotGetTransactionLock) do
28
+ p1.transaction { true }
29
+ end
30
+
31
+ assert_raises(Mongomatic::Exceptions::CannotGetTransactionLock) do
32
+ p1.transaction { true }
33
+ end
34
+
35
+ l.remove!
36
+
37
+ p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
38
+ end
39
+
40
+ def test_will_rm_stale_locks
41
+ p1 = Person.new(:name => "Jordan")
42
+ p1.insert!
43
+
44
+ l = Mongomatic::TransactionLock.new(:key => "Person-#{p1["_id"]}", :expire_at => Time.now.utc - 1.day)
45
+ l.insert!
46
+
47
+ p1.transaction { assert_equal 1, Mongomatic::TransactionLock.count }
48
+ end
49
+
50
+ def test_race_condition
51
+ c = Clock.new
52
+ c.insert!
53
+ threads = []
54
+
55
+ lock_errors = 0
56
+
57
+ 50.times do
58
+ threads << Thread.new do
59
+ begin
60
+ Mongomatic.db = Mongo::Connection.new.db("mongomatic_test")
61
+ c = Clock.find_one(c["_id"])
62
+ c.tick!
63
+ rescue Mongomatic::Exceptions::CannotGetTransactionLock => e
64
+ lock_errors += 1
65
+ sleep(0.05); retry
66
+ end
67
+ end
68
+ end
69
+ threads.each { |th| th.join }
70
+
71
+ c = Clock.find_one(c["_id"])
72
+ assert_equal 50, c["ticks"]
73
+
74
+ assert lock_errors > 0
75
+ end
76
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 7
8
- - 1
9
- version: 0.7.1
8
+ - 2
9
+ version: 0.7.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ben Myles
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-11-18 00:00:00 -08:00
17
+ date: 2010-11-19 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -101,6 +101,7 @@ files:
101
101
  - lib/mongomatic/modifiers.rb
102
102
  - lib/mongomatic/observable.rb
103
103
  - lib/mongomatic/observer.rb
104
+ - lib/mongomatic/transaction_lock.rb
104
105
  - lib/mongomatic/type_converters.rb
105
106
  - lib/mongomatic/typed_fields.rb
106
107
  - lib/mongomatic/util.rb
@@ -113,6 +114,7 @@ files:
113
114
  - test/test_modifiers.rb
114
115
  - test/test_observable.rb
115
116
  - test/test_persistence.rb
117
+ - test/test_transaction_lock.rb
116
118
  - test/test_typed_fields.rb
117
119
  - test/test_validations.rb
118
120
  has_rdoc: true
@@ -120,8 +122,8 @@ homepage: http://mongomatic.com/
120
122
  licenses: []
121
123
 
122
124
  post_install_message:
123
- rdoc_options:
124
- - --charset=UTF-8
125
+ rdoc_options: []
126
+
125
127
  require_paths:
126
128
  - lib
127
129
  required_ruby_version: !ruby/object:Gem::Requirement
@@ -155,5 +157,6 @@ test_files:
155
157
  - test/test_modifiers.rb
156
158
  - test/test_observable.rb
157
159
  - test/test_persistence.rb
160
+ - test/test_transaction_lock.rb
158
161
  - test/test_typed_fields.rb
159
162
  - test/test_validations.rb