retryable 3.0.1 → 3.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 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