retryable 3.0.1 → 3.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
  SHA256:
3
- metadata.gz: 441e4e21d1cbf851bc4ce28f99774b1b0e0058920f401926259b08d311fc6f4d
4
- data.tar.gz: 0c7b6ec7ac61587f68c4186405e00b4f2021558520b692ebe6b013503bccf97d
3
+ metadata.gz: 166388a879ef75725b9a8195680f0018ecaf92d65355171e3755ab45479c9882
4
+ data.tar.gz: 4c89395fee2436620a17586627af3cae189aefc2b60b195682853c73dec7d0e3
5
5
  SHA512:
6
- metadata.gz: 9ff21470b65d4037e2cc88b00f8404d72327cb3891fde218794242b76b7e7d90de8c1e6cb88756effe70886a84d515efb0c08ac4f491962b85f2689423e44d87
7
- data.tar.gz: bcc68f82b11fae2876ae81730c5d953291428b8524dc68e221961666375216004ed447af9ad3eac1b1f7342eb110ab1597320c3a0469afcad4e276383262958d
6
+ metadata.gz: 8669644175373da9208bfacd9978a620eda7b2f0789fbfc2149e1333af74456c91699f3adacbd116502360b0457f781f7d9748a48bf01bc21a885314df0a4269
7
+ data.tar.gz: a4780ebe5eaae4c1f91412dfd2a31948c14467a072cca3f737b3bcbb49b9526bb0fe78669df110879d47a50e6c4b05d4825558bec3df0608d79230d633db9327
@@ -1,3 +1,7 @@
1
+ ## Retryable 3.0.2 ##
2
+
3
+ * :log_method param has been added for flexible logging of your retries. It is silent by default.
4
+
1
5
  ## Retryable 3.0.1 ##
2
6
 
3
7
  * :matching param from now on could be called in form of array with multiple matching conditions. This version is backwards compatible with 3.0.0
data/README.md CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/retryable.svg)](https://badge.fury.io/rb/retryable)
4
4
  [![Build Status](https://travis-ci.org/nfedyashev/retryable.png?branch=master)](https://travis-ci.org/nfedyashev/retryable)
5
- [![Dependency Status](https://www.versioneye.com/ruby/retryable/badge.svg)](https://www.versioneye.com/ruby/retryable)
6
5
  [![Code Climate](https://codeclimate.com/github/nfedyashev/retryable/badges/gpa.svg)](https://codeclimate.com/github/nfedyashev/retryable)
7
6
  [![Test Coverage](https://codeclimate.com/github/nfedyashev/retryable/badges/coverage.svg)](https://codeclimate.com/github/nfedyashev/retryable/coverage)
8
7
 
@@ -57,10 +56,10 @@ end
57
56
  ```
58
57
 
59
58
  Do something, retry up to four times for either `ArgumentError` or
60
- `TimeoutError` exceptions.
59
+ `Timeout::Error` exceptions.
61
60
 
62
61
  ``` ruby
63
- Retryable.retryable(tries: 5, on: [ArgumentError, TimeoutError]) do
62
+ Retryable.retryable(tries: 5, on: [ArgumentError, Timeout::Error]) do
64
63
  # code here
65
64
  end
66
65
  ```
@@ -83,29 +82,31 @@ end
83
82
 
84
83
  ## Defaults
85
84
 
86
- tries: 2,
87
- on: StandardError,
88
- sleep: 1,
89
- matching : /.*/,
85
+ contexts: {},
90
86
  ensure: proc { },
91
87
  exception_cb: proc { },
88
+ log_method: proc { },
89
+ matching : /.*/,
92
90
  not: [],
91
+ on: StandardError,
92
+ sleep: 1,
93
93
  sleep_method: lambda { |n| Kernel.sleep(n) },
94
- contexts: {}
94
+ tries: 2
95
95
 
96
96
  Retryable also could be configured globally to change those defaults:
97
97
 
98
98
  ```ruby
99
99
  Retryable.configure do |config|
100
+ config.contexts = {}
100
101
  config.ensure = proc {}
101
102
  config.exception_cb = proc {}
103
+ config.log_method = proc {}
102
104
  config.matching = /.*/
105
+ config.not = []
103
106
  config.on = StandardError
104
107
  config.sleep = 1
105
- config.tries = 2
106
- config.not = []
107
108
  config.sleep_method = Celluloid.method(:sleep)
108
- config.contexts = {}
109
+ config.tries = 2
109
110
  end
110
111
  ```
111
112
 
@@ -160,6 +161,28 @@ Retryable.retryable(exception_cb: exception_cb) do
160
161
  end
161
162
  ```
162
163
 
164
+ Logging
165
+ --------
166
+
167
+ ```ruby
168
+
169
+ # or extract it to global config instead:
170
+ log_method = lambda do |retries, exception|
171
+ Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
172
+ end
173
+
174
+ Retryable.retryable(log_method: log_method, matching: /IO timeout/) do |retries, exception|
175
+ raise "oops IO timeout!" if retries == 0
176
+ end
177
+ #D, [2018-09-01T18:19:06.093811 #22535] DEBUG -- : [Attempt #1] Retrying because [RuntimeError - oops IO timeout!]: (irb#1):6:in `block in irb_binding' | /home/nikita/Projects/retryable/lib/retryable.rb:73:in `retryable' | (irb#1):6:in `irb_binding' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval' | /home/nikita/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/irb/workspace.rb:85:in `evaluate'
178
+ ```
179
+
180
+ If you prefer to use Rails' native logger:
181
+
182
+ log_method = lambda do |retries, exception|
183
+ Rails.logger.debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
184
+ end
185
+
163
186
  Contexts
164
187
  --------
165
188
 
@@ -1,8 +1,12 @@
1
1
  require 'retryable/version'
2
2
  require 'retryable/configuration'
3
+ require 'forwardable'
3
4
 
5
+ # Runs a code block, and retries it when an exception occurs. It's great when working with flakey webservices (for example).
4
6
  module Retryable
5
7
  class << self
8
+ extend Forwardable
9
+
6
10
  # A Retryable configuration object. Must act like a hash and return sensible
7
11
  # values for all Retryable configuration options. See Retryable::Configuration.
8
12
  attr_writer :configuration
@@ -14,12 +18,13 @@ module Retryable
14
18
  # config.contexts = {}
15
19
  # config.ensure = proc {}
16
20
  # config.exception_cb = proc {}
21
+ # config.log_method = proc {}
17
22
  # config.matching = /.*/
23
+ # config.not = []
18
24
  # config.on = StandardError
19
25
  # config.sleep = 1
20
- # config.tries = 2
21
- # config.not = []
22
26
  # config.sleep_method = ->(seconds) { Kernel.sleep(seconds) }
27
+ # config.tries = 2
23
28
  # end
24
29
  def configure
25
30
  yield(configuration)
@@ -31,8 +36,10 @@ module Retryable
31
36
  @configuration ||= Configuration.new
32
37
  end
33
38
 
39
+ delegate [:enabled?, :enable, :disable] => :configuration
40
+
34
41
  def with_context(context_key, options = {}, &block)
35
- unless configuration.contexts.keys.include? context_key
42
+ unless configuration.contexts.key?(context_key)
36
43
  raise ArgumentError, "#{context_key} not found in Retryable.configuration.contexts. Available contexts: #{configuration.contexts.keys}"
37
44
  end
38
45
  retryable(configuration.contexts[context_key].merge(options), &block) if block
@@ -40,35 +47,28 @@ module Retryable
40
47
 
41
48
  alias retryable_with_context with_context
42
49
 
43
- def enabled?
44
- configuration.enabled?
45
- end
46
-
47
- def enable
48
- configuration.enable
49
- end
50
-
51
- def disable
52
- configuration.disable
53
- end
54
-
50
+ # rubocop:disable Metrics/MethodLength
51
+ # rubocop:disable Metrics/PerceivedComplexity
55
52
  def retryable(options = {})
56
53
  opts = configuration.to_hash
57
54
 
58
55
  check_for_invalid_options(options, opts)
59
56
  opts.merge!(options)
60
57
 
58
+ # rubocop:disable Style/NumericPredicate
61
59
  return if opts[:tries] == 0
60
+ # rubocop:enable Style/NumericPredicate
62
61
 
63
62
  on_exception = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
64
63
  not_exception = opts[:not].is_a?(Array) ? opts[:not] : [opts[:not]]
65
64
 
66
- matching = [opts[:matching]].flatten
65
+ matching = opts[:matching].is_a?(Array) ? opts[:matching] : [opts[:matching]]
67
66
  tries = opts[:tries]
68
67
  retries = 0
69
68
  retry_exception = nil
70
69
 
71
70
  begin
71
+ opts[:log_method].call(retries, retry_exception) if retries > 0
72
72
  return yield retries, retry_exception
73
73
  rescue *not_exception
74
74
  raise
@@ -94,6 +94,8 @@ module Retryable
94
94
  opts[:ensure].call(retries)
95
95
  end
96
96
  end
97
+ # rubocop:enable Metrics/MethodLength
98
+ # rubocop:enable Metrics/PerceivedComplexity
97
99
 
98
100
  private
99
101
 
@@ -111,7 +113,7 @@ module Retryable
111
113
  when Regexp
112
114
  message =~ candidate
113
115
  else
114
- raise ArgumentError, ":matches must be a string or regex"
116
+ raise ArgumentError, ':matches must be a string or regex'
115
117
  end
116
118
  end
117
119
  end
@@ -5,6 +5,7 @@ module Retryable
5
5
  :contexts,
6
6
  :ensure,
7
7
  :exception_cb,
8
+ :log_method,
8
9
  :matching,
9
10
  :not,
10
11
  :on,
@@ -21,12 +22,14 @@ module Retryable
21
22
  @contexts = {}
22
23
  @ensure = proc {}
23
24
  @exception_cb = proc {}
25
+ @log_method = proc {}
24
26
  @matching = /.*/
27
+ @not = []
25
28
  @on = StandardError
26
29
  @sleep = 1
27
- @tries = 2
28
- @not = []
29
30
  @sleep_method = ->(seconds) { Kernel.sleep(seconds) }
31
+ @tries = 2
32
+
30
33
  @enabled = true
31
34
  end
32
35
 
@@ -15,7 +15,7 @@ module Retryable
15
15
 
16
16
  # @return [Integer]
17
17
  def patch
18
- 1
18
+ 2
19
19
  end
20
20
 
21
21
  # @return [Hash]
@@ -1,4 +1,7 @@
1
+ # rubocop:disable Style/ExpandPathArguments
1
2
  lib = File.expand_path('../lib', __FILE__)
3
+ # rubocop:enable Style/ExpandPathArguments
4
+
2
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
6
  require 'retryable/version'
4
7
 
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+ require 'logger'
3
+
4
+ RSpec.describe Retryable do
5
+ describe '.retryable' do
6
+ before do
7
+ described_class.enable
8
+ expect(Kernel).to receive(:sleep)
9
+ end
10
+
11
+ let(:retryable) do
12
+ -> { Retryable.retryable(tries: 2) { |tries| raise StandardError, "because foo" if tries < 1 } }
13
+ end
14
+
15
+ context 'given default configuration' do
16
+ it 'does not output anything' do
17
+ expect { retryable.call }.not_to output.to_stdout_from_any_process
18
+ end
19
+ end
20
+
21
+ context 'given custom STDOUT logger config option' do
22
+ it 'does not output anything' do
23
+ described_class.configure do |config|
24
+ config.log_method = lambda do |retries, exception|
25
+ Logger.new(STDOUT).debug("[Attempt ##{retries}] Retrying because [#{exception.class} - #{exception.message}]: #{exception.backtrace.first(5).join(' | ')}")
26
+ end
27
+ end
28
+
29
+ expect { retryable.call }.to output(/\[Attempt #1\] Retrying because \[StandardError - because foo\]/).to_stdout_from_any_process
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -126,11 +126,11 @@ RSpec.describe Retryable do
126
126
 
127
127
  it 'catches an exception in the list of matches' do
128
128
  expect(Kernel).to receive(:sleep).once.with(1)
129
- counter(matching: [/IO timeout/, "IO tymeout"]) { |c, _e| raise 'yo, IO timeout!' if c == 0 }
129
+ counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO timeout!' if c == 0 }
130
130
  expect(counter.count).to eq(2)
131
131
 
132
132
  expect(Kernel).to receive(:sleep).once.with(1)
133
- counter(matching: [/IO timeout/, "IO tymeout"]) { |c, _e| raise 'yo, IO tymeout!' if c == 0 }
133
+ counter(matching: [/IO timeout/, 'IO tymeout']) { |c, _e| raise 'yo, IO tymeout!' if c == 0 }
134
134
  expect(counter.count).to eq(4)
135
135
  end
136
136
 
@@ -140,6 +140,7 @@ RSpec.describe Retryable do
140
140
  end.to raise_error ArgumentError, '[Retryable] Invalid options: bad_option'
141
141
  end
142
142
 
143
+ # rubocop:disable Rspec/InstanceVariable
143
144
  it 'accepts a callback to run after an exception is rescued' do
144
145
  expect do
145
146
  described_class.retryable(sleep: 0, exception_cb: proc { |e| @raised = e.to_s }) do |tries|
@@ -149,6 +150,7 @@ RSpec.describe Retryable do
149
150
 
150
151
  expect(@raised).to eq('this is fun!')
151
152
  end
153
+ # rubocop:enable Rspec/InstanceVariable
152
154
 
153
155
  it 'does not retry on :not exception' do
154
156
  expect do
@@ -1,7 +1,11 @@
1
1
  require 'retryable'
2
2
  require 'simplecov'
3
3
 
4
+ require 'pry'
5
+
6
+ # rubocop:disable Style/ExpandPathArguments
4
7
  Dir.glob(File.expand_path('../support/**/*.rb', __FILE__), &method(:require))
8
+ # rubocop:enable Style/ExpandPathArguments
5
9
 
6
10
  SimpleCov.start
7
11
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: retryable
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.1
4
+ version: 3.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikita Fedyashev
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-04-11 00:00:00.000000000 Z
13
+ date: 2018-09-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -44,6 +44,7 @@ files:
44
44
  - retryable.gemspec
45
45
  - spec/retryable/configuration_spec.rb
46
46
  - spec/retryable/version_spec.rb
47
+ - spec/retryable_logging_spec.rb
47
48
  - spec/retryable_spec.rb
48
49
  - spec/retryable_with_context_spec.rb
49
50
  - spec/spec_helper.rb
@@ -68,13 +69,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  version: 1.3.6
69
70
  requirements: []
70
71
  rubyforge_project:
71
- rubygems_version: 2.7.3
72
+ rubygems_version: 2.7.6
72
73
  signing_key:
73
74
  specification_version: 4
74
75
  summary: Retrying code blocks in Ruby
75
76
  test_files:
76
77
  - spec/retryable/configuration_spec.rb
77
78
  - spec/retryable/version_spec.rb
79
+ - spec/retryable_logging_spec.rb
78
80
  - spec/retryable_spec.rb
79
81
  - spec/retryable_with_context_spec.rb
80
82
  - spec/spec_helper.rb