retryable_record 0.2.0 → 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/LICENSE +2 -0
- data/README.rdoc +29 -1
- data/lib/retryable_record.rb +27 -7
- data/lib/retryable_record/import.rb +11 -2
- data/lib/retryable_record/version.rb +1 -1
- data/retryable_record.gemspec +2 -1
- data/test/helper.rb +19 -32
- data/test/retryable_record_import_test.rb +17 -3
- data/test/retryable_record_test.rb +19 -8
- data/test/support/fake_record.rb +40 -0
- metadata +19 -32
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fd3a3c47c9c5b116e53ab13546b73a141c5c6248
|
4
|
+
data.tar.gz: 1e4f1d3b5dd0ff89a5cda4f4389ee46fb05f2dff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5d6b2ab96bb4f6eda6f901aec0f294d29a69d366fe960b356478ea2d937b4d331ff7f86a13b62d6f46b2cebf61d1b019d48211d8ca263f56539cb87b5cdea17
|
7
|
+
data.tar.gz: 6c74189f910b32da855c101a9405cd5fd27ebda3f2005cf788b54a5c36690ce908ee23968b4d9eac5a5fcd8b699fbd7dfde01c119cdd3d570439439b0dfc32d3
|
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -46,6 +46,29 @@ You can use +retryable_record+ in 3 different ways:
|
|
46
46
|
user.save!
|
47
47
|
end
|
48
48
|
|
49
|
+
== Option +attempts+
|
50
|
+
|
51
|
+
There is also an option +attempts+ to limit the number of retries.
|
52
|
+
If no attempts option is specified, it's assumed to be possibly infinte attempts until
|
53
|
+
an ActiveRecord::StaleObjectError is not raised. The +attempts+ option works in all three forms.
|
54
|
+
|
55
|
+
Here is the Module inclusion example with an attempts option used.
|
56
|
+
|
57
|
+
require 'retryable_record'
|
58
|
+
|
59
|
+
class User < ActiveRecord::Base
|
60
|
+
include RetryableRecord
|
61
|
+
end
|
62
|
+
|
63
|
+
user = User.first
|
64
|
+
|
65
|
+
user.retryable(:attempts => 5) do
|
66
|
+
user.username = "foo"
|
67
|
+
user.save!
|
68
|
+
end
|
69
|
+
|
70
|
+
After 5 attempts, this will just re-raise the ActiveRecord::StaleObjectError anyway.
|
71
|
+
|
49
72
|
== Optimistic locking (lock_version column)
|
50
73
|
|
51
74
|
ActiveRecord migration needs to support optimistic locking. See http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
@@ -69,9 +92,14 @@ Inspired by
|
|
69
92
|
* http://github.com/nfedyashev/retryable
|
70
93
|
* http://vision-media.ca/resources/ruby/better-ruby-retryable-method (broken)
|
71
94
|
|
95
|
+
=== Contributors
|
96
|
+
|
97
|
+
Thanks to all contributions from awesome people[https://github.com/neopoly/retryable_record/contributors]!
|
98
|
+
|
72
99
|
== TODO
|
73
100
|
|
74
|
-
*
|
101
|
+
* Improve README example
|
102
|
+
* Add Changelog!
|
75
103
|
* Intergration test with ActiveRecord
|
76
104
|
|
77
105
|
== Note on Patches/Pull Requests
|
data/lib/retryable_record.rb
CHANGED
@@ -16,6 +16,19 @@ require 'active_record/base'
|
|
16
16
|
# user.save!
|
17
17
|
# end
|
18
18
|
#
|
19
|
+
# == Example using attempts
|
20
|
+
#
|
21
|
+
# class User < ActiveRecord::Base
|
22
|
+
# include RetryableRecord
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# user = User.first
|
26
|
+
#
|
27
|
+
# user.retryable(:attempts => 2) do
|
28
|
+
# user.username = "foo"
|
29
|
+
# user.save!
|
30
|
+
# end
|
31
|
+
#
|
19
32
|
module RetryableRecord
|
20
33
|
# Retryable operations on an ActiveRecord +record+.
|
21
34
|
#
|
@@ -26,16 +39,23 @@ module RetryableRecord
|
|
26
39
|
# user.save!
|
27
40
|
# end
|
28
41
|
#
|
29
|
-
def retry(record)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
42
|
+
def retry(record, opts = {})
|
43
|
+
attempts = opts[:attempts]
|
44
|
+
begin
|
45
|
+
yield
|
46
|
+
rescue ActiveRecord::StaleObjectError
|
47
|
+
unless attempts.nil?
|
48
|
+
raise unless attempts > 0
|
49
|
+
attempts -= 1
|
50
|
+
end
|
51
|
+
record.reload
|
52
|
+
retry
|
53
|
+
end
|
34
54
|
end
|
35
55
|
module_function :retry
|
36
56
|
|
37
57
|
# Retries operations on an ActiveRecord.
|
38
|
-
def retryable(&block)
|
39
|
-
RetryableRecord.retry(self, &block)
|
58
|
+
def retryable(opts = {}, &block)
|
59
|
+
RetryableRecord.retry(self, opts, &block)
|
40
60
|
end
|
41
61
|
end
|
@@ -12,8 +12,17 @@ module Kernel
|
|
12
12
|
# user.save!
|
13
13
|
# end
|
14
14
|
#
|
15
|
+
# == Example using attempts
|
16
|
+
#
|
17
|
+
# require 'retryable_record/import'
|
18
|
+
#
|
19
|
+
# RetryableRecord(user, :attempts => 3) do
|
20
|
+
# user.username = "foo"
|
21
|
+
# user.save!
|
22
|
+
# end
|
23
|
+
#
|
15
24
|
# See RetryableRecord#retry
|
16
|
-
def RetryableRecord(record, &block)
|
17
|
-
RetryableRecord.retry(record, &block)
|
25
|
+
def RetryableRecord(record, opts = {}, &block)
|
26
|
+
RetryableRecord.retry(record, opts, &block)
|
18
27
|
end
|
19
28
|
end
|
data/retryable_record.gemspec
CHANGED
@@ -14,10 +14,11 @@ Gem::Specification.new do |gem|
|
|
14
14
|
gem.name = "retryable_record"
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = RetryableRecord::VERSION
|
17
|
+
gem.license = 'MIT'
|
17
18
|
|
18
19
|
gem.add_runtime_dependency 'activerecord', '>= 3'
|
19
20
|
|
20
21
|
gem.add_development_dependency 'rake'
|
21
22
|
gem.add_development_dependency 'rdoc'
|
22
|
-
gem.add_development_dependency 'minitest', '~>
|
23
|
+
gem.add_development_dependency 'minitest', '~> 5.0.4'
|
23
24
|
end
|
data/test/helper.rb
CHANGED
@@ -1,40 +1,27 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
|
3
|
-
require 'minitest/unit'
|
4
3
|
require 'minitest/autorun'
|
5
4
|
|
6
5
|
require 'retryable_record'
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@counter[:save] += 1
|
28
|
-
end
|
29
|
-
|
30
|
-
def retries_left?
|
31
|
-
@counter[:retries_left] > 0
|
32
|
-
end
|
33
|
-
|
34
|
-
def concurrent_modification!
|
35
|
-
if retries_left?
|
36
|
-
@counter[:retries_left] -= 1
|
37
|
-
raise ActiveRecord::StaleObjectError.new self, :save
|
38
|
-
end
|
7
|
+
require 'support/fake_record'
|
8
|
+
|
9
|
+
class Spec < Minitest::Spec
|
10
|
+
# Assert internal counter of the fake +record+.
|
11
|
+
#
|
12
|
+
# The following counters must be provided:
|
13
|
+
# * +:reloads+
|
14
|
+
# * +:saves+
|
15
|
+
#
|
16
|
+
# == Example
|
17
|
+
#
|
18
|
+
# assert_record :reloads => 2, :saves => 1
|
19
|
+
#
|
20
|
+
def assert_record(counter={})
|
21
|
+
raise ":reloads missing" unless counter.key?(:reloads)
|
22
|
+
raise ":saves missing" unless counter.key?(:saves)
|
23
|
+
|
24
|
+
assert_equal counter[:reloads], record.count_reloads, "unexpected record reloads"
|
25
|
+
assert_equal counter[:saves], record.count_saves, "unexpected record saves"
|
39
26
|
end
|
40
27
|
end
|
@@ -15,9 +15,23 @@ class RetryableRecordImportTest < Spec
|
|
15
15
|
|
16
16
|
let(:retries) { 0 }
|
17
17
|
|
18
|
-
it "saves and does not retry" do
|
19
|
-
|
20
|
-
|
18
|
+
it "saves and does not retry/reload" do
|
19
|
+
assert_record :reloads => 0, :saves => 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe :RetryableRecord_with_attempts do
|
24
|
+
let(:retries) { 2 }
|
25
|
+
|
26
|
+
it "retries `attempts` times, before re-raising" do
|
27
|
+
assert_raises ActiveRecord::StaleObjectError do
|
28
|
+
RetryableRecord(record, :attempts => 1) do
|
29
|
+
record.concurrent_modification!
|
30
|
+
record.save!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
assert_record :reloads => 1, :saves => 0
|
21
35
|
end
|
22
36
|
end
|
23
37
|
end
|
@@ -16,8 +16,7 @@ class RetryableRecordTest < Spec
|
|
16
16
|
let(:retries) { 0 }
|
17
17
|
|
18
18
|
it "saves and does not retry" do
|
19
|
-
|
20
|
-
assert_equal 1, record.counter[:save]
|
19
|
+
assert_record :reloads => 0, :saves => 1
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
@@ -25,8 +24,7 @@ class RetryableRecordTest < Spec
|
|
25
24
|
let(:retries) { 5 }
|
26
25
|
|
27
26
|
it "saves and reloads 5 times" do
|
28
|
-
|
29
|
-
assert_equal 1, record.counter[:save]
|
27
|
+
assert_record :reloads => 5, :saves => 1
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
@@ -37,8 +35,7 @@ class RetryableRecordTest < Spec
|
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
40
|
-
|
41
|
-
assert_equal 1, record.counter[:save]
|
38
|
+
assert_record :reloads => 0, :saves => 1
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
@@ -53,8 +50,22 @@ class RetryableRecordTest < Spec
|
|
53
50
|
let(:retries) { 0 }
|
54
51
|
|
55
52
|
it "saves and does not retry" do
|
56
|
-
|
57
|
-
|
53
|
+
assert_record :reloads => 0, :saves => 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe :retryable_with_attempts do
|
58
|
+
let(:retries) { 2 }
|
59
|
+
|
60
|
+
it "retries `attempts` times, before re-raising" do
|
61
|
+
assert_raises ActiveRecord::StaleObjectError do
|
62
|
+
record.retryable(:attempts => 1) do
|
63
|
+
record.concurrent_modification!
|
64
|
+
record.save!
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
assert_record :reloads => 1, :saves => 0
|
58
69
|
end
|
59
70
|
end
|
60
71
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class FakeRecord
|
2
|
+
include RetryableRecord
|
3
|
+
|
4
|
+
def initialize(retries_left = 0)
|
5
|
+
@counter = Hash.new(0)
|
6
|
+
@counter[:retries_left] = retries_left
|
7
|
+
end
|
8
|
+
|
9
|
+
def reload
|
10
|
+
counter[:reloads] += 1
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def save!
|
15
|
+
counter[:saves] += 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def count_saves
|
19
|
+
counter[:saves]
|
20
|
+
end
|
21
|
+
|
22
|
+
def count_reloads
|
23
|
+
counter[:reloads]
|
24
|
+
end
|
25
|
+
|
26
|
+
def concurrent_modification!
|
27
|
+
if retries_left?
|
28
|
+
counter[:retries_left] -= 1
|
29
|
+
raise ActiveRecord::StaleObjectError.new self, :save
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :counter
|
36
|
+
|
37
|
+
def retries_left?
|
38
|
+
counter[:retries_left] > 0
|
39
|
+
end
|
40
|
+
end
|
metadata
CHANGED
@@ -1,80 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: retryable_record
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Peter Suschlik
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2013-
|
11
|
+
date: 2013-06-13 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activerecord
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - '>='
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '3'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - '>='
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '3'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - '>='
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - '>='
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rdoc
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - '>='
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - '>='
|
60
53
|
- !ruby/object:Gem::Version
|
61
54
|
version: '0'
|
62
55
|
- !ruby/object:Gem::Dependency
|
63
56
|
name: minitest
|
64
57
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
58
|
requirements:
|
67
59
|
- - ~>
|
68
60
|
- !ruby/object:Gem::Version
|
69
|
-
version:
|
61
|
+
version: 5.0.4
|
70
62
|
type: :development
|
71
63
|
prerelease: false
|
72
64
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
65
|
requirements:
|
75
66
|
- - ~>
|
76
67
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
68
|
+
version: 5.0.4
|
78
69
|
description: Retries an operation on an ActiveRecord until no StaleObjectError is
|
79
70
|
being raised.
|
80
71
|
email:
|
@@ -99,38 +90,34 @@ files:
|
|
99
90
|
- test/helper.rb
|
100
91
|
- test/retryable_record_import_test.rb
|
101
92
|
- test/retryable_record_test.rb
|
93
|
+
- test/support/fake_record.rb
|
102
94
|
homepage: https://github.com/neopoly/retryable_record
|
103
|
-
licenses:
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
104
98
|
post_install_message:
|
105
99
|
rdoc_options: []
|
106
100
|
require_paths:
|
107
101
|
- lib
|
108
102
|
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
-
none: false
|
110
103
|
requirements:
|
111
|
-
- -
|
104
|
+
- - '>='
|
112
105
|
- !ruby/object:Gem::Version
|
113
106
|
version: '0'
|
114
|
-
segments:
|
115
|
-
- 0
|
116
|
-
hash: -864916261665633026
|
117
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
-
none: false
|
119
108
|
requirements:
|
120
|
-
- -
|
109
|
+
- - '>='
|
121
110
|
- !ruby/object:Gem::Version
|
122
111
|
version: '0'
|
123
|
-
segments:
|
124
|
-
- 0
|
125
|
-
hash: -864916261665633026
|
126
112
|
requirements: []
|
127
113
|
rubyforge_project:
|
128
|
-
rubygems_version:
|
114
|
+
rubygems_version: 2.0.3
|
129
115
|
signing_key:
|
130
|
-
specification_version:
|
116
|
+
specification_version: 4
|
131
117
|
summary: Retries an operation on an ActiveRecord until no StaleObjectError is being
|
132
118
|
raised.
|
133
119
|
test_files:
|
134
120
|
- test/helper.rb
|
135
121
|
- test/retryable_record_import_test.rb
|
136
122
|
- test/retryable_record_test.rb
|
123
|
+
- test/support/fake_record.rb
|