recoverable 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a004cdad4e2c84084abd80d9f768e76e68ef82e
4
- data.tar.gz: 619f7e3dfb8ecb873afaa6f63c1daab89afa022c
3
+ metadata.gz: 8dea1219bb101ee0ff89ca0794c8a4c2648275c0
4
+ data.tar.gz: a82f71d6ade61164f07701c5c1bd26b375e235cb
5
5
  SHA512:
6
- metadata.gz: 27fb6b8a72f506a7f19af2bafd2508a59bc61dd0582af89ab24105f7c36105c57ba42bcbbf8647d4104ad3a93ebb9cd90eef5e37094cdf8e9df0258cf90680f8
7
- data.tar.gz: 0fb7fafc2453bbdbe8427acb02d904708c072fd78fe3c202e5ea587f4f064920640ae94bfccfaad1d2641ff37ddc27e3be9fc0a9dc978f943501ca60c359f6b8
6
+ metadata.gz: a425e9704da44b71696b128c71f4ff4b42934433335a214befc5467d887fb28ffd32f4cdd7757311ed7264a79707ec5a1b12ac2270be2378e9c8f85f6e54205a
7
+ data.tar.gz: affcc22857a7fffba3ddc96b326b9347429fd78e021ffca345ac99ca5b98f7bd69e393a443a282076b99b76503555746c14154d7f7831b58171da60eaed6b850
data/README.md CHANGED
@@ -1,3 +1,8 @@
1
+ [![Version ](https://img.shields.io/gem/v/recoverable.svg?maxAge=2592000)](https://rubygems.org/gems/recoverable)
2
+ [![Build Status ](https://travis-ci.com/Benjaminpjacobs/ship_station.svg)](https://travis-ci.com/Benjaminpjacobs/recoverable)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/dd436c45c8a52dc8c13c/maintainability)](https://codeclimate.com/github/Benjaminpjacobs/recoverable/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/dd436c45c8a52dc8c13c/test_coverage)](https://codeclimate.com/github/Benjaminpjacobs/recoverable/test_coverage)
5
+
1
6
  ## Recoverbale
2
7
 
3
8
 
@@ -1,46 +1,69 @@
1
- module Retryable
2
- RetryCountExceeded = Class.new(StandardError)
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 = create_proxy(method, times, recoverable, sleep, handler)
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(method, times, recoverable, sleep, handler)
8
+ def create_proxy(method_name:, times:, recoverable:, sleep:, custom_handler:, custom_exception:)
11
9
  Module.new do
12
- define_method(method) do |*args|
13
- retries = times.dup
10
+ define_method(method_name) do |*args|
14
11
  begin
15
12
  super *args
16
13
  rescue *recoverable => error
17
- begin
18
- sleep(sleep)
19
- retries.next && retry
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
 
@@ -0,0 +1,7 @@
1
+ module Recoverable
2
+ class RetryCountExceeded < StandardError
3
+ def initialize(error)
4
+ super error
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module Recoverable
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/recoverable.rb CHANGED
@@ -3,6 +3,7 @@ require 'active_support/all'
3
3
  require 'logger'
4
4
 
5
5
  # App
6
+ require 'recoverable/errors'
6
7
  require 'recoverable/base'
7
8
 
8
9
  # Models
@@ -0,0 +1 @@
1
+ class CustomError < StandardError; end
data/spec/mocks/foo.rb ADDED
@@ -0,0 +1,10 @@
1
+ class Foo
2
+ extend Recoverable
3
+
4
+ def bar
5
+ baz
6
+ end
7
+
8
+ def baz
9
+ end
10
+ end
@@ -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
@@ -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.1
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