recoverable 0.0.1 → 0.0.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.
- checksums.yaml +4 -4
- data/README.md +5 -0
- data/lib/recoverable/base.rb +54 -31
- data/lib/recoverable/errors.rb +7 -0
- data/lib/recoverable/version.rb +1 -1
- data/lib/recoverable.rb +1 -0
- data/spec/mocks/custom_error.rb +1 -0
- data/spec/mocks/foo.rb +10 -0
- data/spec/mocks/unrecovered_error.rb +1 -0
- data/spec/recoverable_spec.rb +178 -0
- data/spec/spec_helper.rb +33 -0
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8dea1219bb101ee0ff89ca0794c8a4c2648275c0
|
4
|
+
data.tar.gz: a82f71d6ade61164f07701c5c1bd26b375e235cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a425e9704da44b71696b128c71f4ff4b42934433335a214befc5467d887fb28ffd32f4cdd7757311ed7264a79707ec5a1b12ac2270be2378e9c8f85f6e54205a
|
7
|
+
data.tar.gz: affcc22857a7fffba3ddc96b326b9347429fd78e021ffca345ac99ca5b98f7bd69e393a443a282076b99b76503555746c14154d7f7831b58171da60eaed6b850
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://rubygems.org/gems/recoverable)
|
2
|
+
[](https://travis-ci.com/Benjaminpjacobs/recoverable)
|
3
|
+
[](https://codeclimate.com/github/Benjaminpjacobs/recoverable/maintainability)
|
4
|
+
[](https://codeclimate.com/github/Benjaminpjacobs/recoverable/test_coverage)
|
5
|
+
|
1
6
|
## Recoverbale
|
2
7
|
|
3
8
|
|
data/lib/recoverable/base.rb
CHANGED
@@ -1,46 +1,69 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
def recover(method, times, on: [StandardError], sleep: 1, handler: nil)
|
1
|
+
module Recoverable
|
2
|
+
def recover(method_name, times: 1, on: StandardError, sleep: nil, custom_handler: nil, custom_exception: RetryCountExceeded)
|
5
3
|
recoverable = Array.wrap(on)
|
6
|
-
proxy
|
4
|
+
proxy = create_proxy(method_name: method_name, times: times, recoverable: recoverable, sleep: sleep, custom_handler: custom_handler, custom_exception: custom_exception)
|
7
5
|
self.prepend proxy
|
8
6
|
end
|
9
7
|
|
10
|
-
def create_proxy(
|
8
|
+
def create_proxy(method_name:, times:, recoverable:, sleep:, custom_handler:, custom_exception:)
|
11
9
|
Module.new do
|
12
|
-
define_method(
|
13
|
-
retries = times.dup
|
10
|
+
define_method(method_name) do |*args|
|
14
11
|
begin
|
15
12
|
super *args
|
16
13
|
rescue *recoverable => error
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
rescue StopIteration
|
21
|
-
if handler
|
22
|
-
handler_args = {}
|
23
|
-
unbound = self.class.instance_method(handler)
|
24
|
-
req_params = unbound.parameters.map{|p| p[1] if p[0] == :keyreq }.compact
|
25
|
-
|
26
|
-
return send(handler) if req_params.empty?
|
27
|
-
|
28
|
-
local_args = args.map(&:to_a).flatten(1).to_h
|
29
|
-
ivs = instance_values.keys.map(&:to_sym)
|
30
|
-
|
31
|
-
req_params.each do |key|
|
32
|
-
handler_args[key] ||= eval(key.to_s) if (ivs + public_methods + local_variables).include?(key)
|
33
|
-
handler_args[key] ||= local_args[key] if local_args.keys.include?(key)
|
34
|
-
end
|
35
|
-
|
36
|
-
send(handler, handler_args)
|
37
|
-
end
|
38
|
-
raise RetryCountExceeded, [error.class, error.message]
|
39
|
-
end
|
14
|
+
sleep(sleep) if sleep
|
15
|
+
retry if (times -= 1) > 0
|
16
|
+
self.class.handle_exception(instance: self, custom_handler: custom_handler, args: args, error: error, custom_exception: custom_exception)
|
40
17
|
end
|
41
18
|
end
|
42
19
|
end
|
43
20
|
end
|
21
|
+
|
22
|
+
def handle_exception(instance:, custom_handler:, args:, error:, custom_exception: )
|
23
|
+
raise custom_exception.new(error) unless custom_handler
|
24
|
+
req_params = retrieve_required_parameters(instance, custom_handler)
|
25
|
+
|
26
|
+
return instance.send(custom_handler) if req_params.empty?
|
27
|
+
|
28
|
+
custom_handler_args = fetch_handler_args(args, instance, req_params, error)
|
29
|
+
instance.send(custom_handler, custom_handler_args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve_required_parameters(instance, custom_handler)
|
33
|
+
unbound_method = instance.class.instance_method(custom_handler)
|
34
|
+
unbound_method.parameters.map{|p| p[1] if p[0] == :keyreq }.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
def fetch_handler_args(args, instance, req_params, error)
|
38
|
+
custom_handler_args = {error: error}
|
39
|
+
local_args = fetch_local_args(args)
|
40
|
+
evaluateables = evaluateable_keys_on(instance)
|
41
|
+
generate_custom_handler_args(req_params: req_params, evaluateables: evaluateables, local_args: local_args, instance: instance, custom_handler_args: custom_handler_args)
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_custom_handler_args(req_params:, evaluateables:, local_args:, instance:, custom_handler_args:)
|
45
|
+
req_params.each do |key|
|
46
|
+
custom_handler_args[key] = instance.send(:eval, key.to_s) if evaluateables.include?(key)
|
47
|
+
custom_handler_args[key] ||= local_args[key] if local_args.keys.include?(key)
|
48
|
+
end
|
49
|
+
|
50
|
+
custom_handler_args
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_instance_variables(instance)
|
54
|
+
instance.instance_values.keys.map(&:to_sym)
|
55
|
+
end
|
56
|
+
|
57
|
+
def fetch_local_args(args)
|
58
|
+
args.map(&:to_a).flatten(1).to_h
|
59
|
+
end
|
60
|
+
|
61
|
+
def evaluateable_keys_on(instance)
|
62
|
+
fetch_instance_variables(instance) +
|
63
|
+
instance.send(:public_methods) +
|
64
|
+
instance.send(:local_variables)
|
65
|
+
end
|
66
|
+
|
44
67
|
end
|
45
68
|
|
46
69
|
|
data/lib/recoverable/version.rb
CHANGED
data/lib/recoverable.rb
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
class CustomError < StandardError; end
|
data/spec/mocks/foo.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
class UnrecoveredError < StandardError; end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
RSpec.describe Recoverable do
|
3
|
+
let!(:instance) { self.class::TestClass.new }
|
4
|
+
subject{ instance.bar }
|
5
|
+
context "passing no errors" do
|
6
|
+
class self::TestClass
|
7
|
+
extend Recoverable
|
8
|
+
recover :bar, times: 2
|
9
|
+
|
10
|
+
def bar; baz; end
|
11
|
+
def baz; end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
it "defaults to standard error and no sleep" do
|
16
|
+
expect_any_instance_of(Kernel).to receive(:sleep).never
|
17
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(StandardError)
|
18
|
+
expect{ subject }.to raise_error(Recoverable::RetryCountExceeded)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "recovers from inheritors of standard error" do
|
22
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
23
|
+
expect{ subject }.to raise_error(Recoverable::RetryCountExceeded)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
context "passing specific errors" do
|
29
|
+
class self::TestClass
|
30
|
+
extend Recoverable
|
31
|
+
recover :bar, times: 2, on: CustomError
|
32
|
+
|
33
|
+
def bar; baz; end
|
34
|
+
def baz; end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
it "recovers from specific error" do
|
39
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
40
|
+
expect{ subject }.to raise_error(Recoverable::RetryCountExceeded)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not recover from different raised error" do
|
44
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(UnrecoveredError)
|
45
|
+
expect{ subject }.to raise_error(UnrecoveredError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "does not recover from standard error" do
|
49
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(StandardError)
|
50
|
+
expect{ subject }.to raise_error(StandardError)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
context "passing specific sleep" do
|
56
|
+
class self::TestClass
|
57
|
+
extend Recoverable
|
58
|
+
recover :bar, times: 2, on: CustomError, sleep: 3
|
59
|
+
|
60
|
+
def bar; baz; end
|
61
|
+
def baz; end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
it "recovers from specific error" do
|
66
|
+
expect_any_instance_of(Kernel).to receive(:sleep).with(3).exactly(2).times
|
67
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
68
|
+
expect{ subject }.to raise_error(Recoverable::RetryCountExceeded)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "passing custom error handler" do
|
73
|
+
class self::TestClass
|
74
|
+
extend Recoverable
|
75
|
+
recover :bar, times: 2, on: CustomError, custom_handler: :handle_error
|
76
|
+
|
77
|
+
def bar; baz; end
|
78
|
+
def baz; end
|
79
|
+
def handle_error
|
80
|
+
"Handled"
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
it "recovers from specific error" do
|
86
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
87
|
+
expect{ subject }.to_not raise_error(Recoverable::RetryCountExceeded)
|
88
|
+
expect(subject).to eq("Handled")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "passing custom error handler with error message" do
|
93
|
+
class self::TestClass
|
94
|
+
extend Recoverable
|
95
|
+
recover :bar, times: 2, on: CustomError, custom_handler: :handle_error
|
96
|
+
|
97
|
+
def bar(arg:nil)
|
98
|
+
baz
|
99
|
+
end
|
100
|
+
|
101
|
+
def baz; end
|
102
|
+
|
103
|
+
def handle_error(error:)
|
104
|
+
error.message
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
subject { instance.bar(arg: "I'm a keyword Arg")}
|
110
|
+
|
111
|
+
it "recovers from specific error" do
|
112
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError.new("Custom Error!"))
|
113
|
+
expect{ subject }.to_not raise_error(Recoverable::RetryCountExceeded)
|
114
|
+
expect(subject).to eq("Custom Error!")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context "passing custom error handler with arg" do
|
119
|
+
class self::TestClass
|
120
|
+
extend Recoverable
|
121
|
+
recover :bar, times: 2, on: CustomError, custom_handler: :handle_error
|
122
|
+
|
123
|
+
def bar(arg:nil)
|
124
|
+
baz
|
125
|
+
end
|
126
|
+
|
127
|
+
def baz; end
|
128
|
+
|
129
|
+
def handle_error(error:, arg:)
|
130
|
+
arg
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
subject { instance.bar(arg: "I'm a keyword Arg")}
|
136
|
+
|
137
|
+
it "recovers from specific error" do
|
138
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
139
|
+
expect{ subject }.to_not raise_error(Recoverable::RetryCountExceeded)
|
140
|
+
expect(subject).to eq("I'm a keyword Arg")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
context "passing custom error handler with a method call" do
|
145
|
+
class self::TestClass
|
146
|
+
extend Recoverable
|
147
|
+
recover :bar, times: 2, on: CustomError, custom_handler: :handle_error
|
148
|
+
|
149
|
+
def bar(arg:nil)
|
150
|
+
baz
|
151
|
+
end
|
152
|
+
|
153
|
+
def baz; end
|
154
|
+
|
155
|
+
def handle_error(error:)
|
156
|
+
"#{method_call}, #{private_method_call}"
|
157
|
+
end
|
158
|
+
|
159
|
+
def method_call
|
160
|
+
"I'm a method call"
|
161
|
+
end
|
162
|
+
private
|
163
|
+
|
164
|
+
def private_method_call
|
165
|
+
"I'm a private method call"
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
subject { instance.bar(arg: "I'm a keyword Arg")}
|
171
|
+
|
172
|
+
it "recovers from specific error" do
|
173
|
+
allow_any_instance_of(self.class::TestClass).to receive(:baz).and_raise(CustomError)
|
174
|
+
expect{ subject }.to_not raise_error(Recoverable::RetryCountExceeded)
|
175
|
+
expect(subject).to eq( "I'm a method call, I'm a private method call")
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'simplecov'
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
SimpleCov.start do
|
6
|
+
add_filter '/spec'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'recoverable'
|
10
|
+
require 'mocks/foo'
|
11
|
+
require 'mocks/custom_error'
|
12
|
+
require 'mocks/unrecovered_error'
|
13
|
+
require 'factory_bot'
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
# Enable flags like --only-failures and --next-failure
|
17
|
+
config.example_status_persistence_file_path = ".rspec_status"
|
18
|
+
|
19
|
+
# Disable RSpec exposing methods globally on `Module` and `main`
|
20
|
+
config.disable_monkey_patching!
|
21
|
+
|
22
|
+
config.expect_with :rspec do |c|
|
23
|
+
c.syntax = :expect
|
24
|
+
end
|
25
|
+
config.include FactoryBot::Syntax::Methods
|
26
|
+
|
27
|
+
config.before(:suite) do
|
28
|
+
FactoryBot.find_definitions
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
RSpec::Expectations.configuration.on_potential_false_positives = :nothing
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: recoverable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Jacobs
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: factory_bot
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.0'
|
97
111
|
description: Class Level retry DSL for ruby
|
98
112
|
email:
|
99
113
|
- benjaminpjacobs@gmail.com
|
@@ -105,7 +119,13 @@ files:
|
|
105
119
|
- README.md
|
106
120
|
- lib/recoverable.rb
|
107
121
|
- lib/recoverable/base.rb
|
122
|
+
- lib/recoverable/errors.rb
|
108
123
|
- lib/recoverable/version.rb
|
124
|
+
- spec/mocks/custom_error.rb
|
125
|
+
- spec/mocks/foo.rb
|
126
|
+
- spec/mocks/unrecovered_error.rb
|
127
|
+
- spec/recoverable_spec.rb
|
128
|
+
- spec/spec_helper.rb
|
109
129
|
homepage:
|
110
130
|
licenses: []
|
111
131
|
metadata:
|
@@ -130,4 +150,9 @@ rubygems_version: 2.6.14.1
|
|
130
150
|
signing_key:
|
131
151
|
specification_version: 4
|
132
152
|
summary: Class Level retry DSL for ruby
|
133
|
-
test_files:
|
153
|
+
test_files:
|
154
|
+
- spec/mocks/foo.rb
|
155
|
+
- spec/mocks/custom_error.rb
|
156
|
+
- spec/mocks/unrecovered_error.rb
|
157
|
+
- spec/spec_helper.rb
|
158
|
+
- spec/recoverable_spec.rb
|