Skip to content

Commit

Permalink
Accept Proc in the :on argument
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelsales committed Oct 14, 2017
1 parent 154f865 commit 772867a
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Fancier README
* Remove unnecessary short circuit in `randomize` method
* Add ability for `:on` argument to accept a `Hash` where the keys are exception types and the values are either `Proc`s or arrays of `Proc`s that will evaluate to `true` when the exception should be retried.

## 3.1.1

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ Here are the available options, in some vague order of relevance to most common
- An `Array` of `Exception` classes (retry any exception of one of these types, including subclasses)
- A `Hash` where the keys are `Exception` classes and the values are one of:
- `nil` (retry every exception of the key's type, including subclasses)
- A single `Proc` (retries exceptions ONLY if it returns truthy)
- A single `Regexp` pattern (retries exceptions ONLY if their `message` matches the pattern)
- An array of patterns (retries exceptions ONLY if their `message` matches at least one of the patterns)
- An array of `Proc` and/or `Regexp` (retries exceptions ONLY if at least one exception matches `Regexp` or the `Proc` evaluates to `true`)


### Configuration
Expand Down Expand Up @@ -129,12 +130,17 @@ Retriable.retriable(on: [Timeout::Error, Errno::ECONNRESET]) do
end
```

You can also specify a Hash of exceptions where the values are either `nil`, a single `Regexp` pattern, or an array of `Regexp`s.
You can also specify a `Hash` where the keys are `Exception` subclasses and the values are either:

A `Regexp` (or array of `Regexp`s). If any of the `Regexp`s match the exception's message, the block will be retried.
A `Proc` (or array of `Proc`s) that evaluates the exception being handled and returns `true` if the block should be retried. If any of the procs in the list return `true`, the block will be retried.
You can also mix and match `Proc`s and `Regexp`s in an `Array`

```ruby
Retriable.retriable(on: {
ActiveRecord::RecordNotUnique => nil,
ActiveRecord::RecordInvalid => [/Email has already been taken/, /Username has already been taken/],
ActiveRecord::RecordNotFound => -> (exception, try, elapsed_time, next_interval) { exception.model == User }
Mysql2::Error => /Duplicate entry/
}) do
# code here...
Expand Down
28 changes: 22 additions & 6 deletions lib/retriable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,33 @@ def retriable(opts = {})
return Timeout.timeout(timeout) { return yield(try) } if timeout
return yield(try)
rescue *[*exception_list] => exception
if on.is_a?(Hash)
raise unless exception_list.any? do |e|
exception.is_a?(e) && ([*on[e]].empty? || [*on[e]].any? { |pattern| exception.message =~ pattern })
end
end

interval = intervals[index]
raise unless matched_exception?(on, exception, try, elapsed_time.call, interval)

on_retry.call(exception, try, elapsed_time.call, interval) if on_retry
raise if try >= tries || (elapsed_time.call + interval) > max_elapsed_time
sleep interval if sleep_disabled != true
end
end
end

def matched_exception?(on, exception, *additional_args)
return true unless on.is_a?(Hash)

on.any? do |expected_exception, matchers|
next false unless exception.is_a?(expected_exception)
next true if matchers.nil?

Array(matchers).any? do |matcher|
if matcher.is_a?(Regexp)
exception.message =~ matcher
elsif matcher.is_a?(Proc)
matcher.call(exception, *additional_args)
else
raise ArgumentError, 'Exception hash values must be Proc or Regexp'
end
end
end
end
private_class_method :matched_exception?
end
28 changes: 28 additions & 0 deletions spec/retriable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,34 @@ class SecondTestError < TestError; end
expect(exceptions[3].class).must_equal StandardError
end

it '#retriable retries with a hash exception where the value is a proc that returns true' do
matcher = ->(e, _try, _elapsed_time, _next_interval) {
e.message == 'something went wrong'
}
tries = 0
expect do
subject.retriable on: { TestError => matcher }, tries: 2 do
tries += 1
raise TestError, 'something went wrong'
end
end.must_raise TestError

expect(tries).must_equal 2
end

it '#retriable does not retry with a hash exception where the value is a proc that returns false' do
matcher = ->(e, *_args) { e.message == 'something went wrong' }
tries = 0
expect do
subject.retriable on: { TestError => matcher }, tries: 2 do
tries += 1
raise TestError, 'not a match'
end
end.must_raise TestError

expect(tries).must_equal 1
end

it "#retriable can be called in the global scope" do
expect do
retriable do
Expand Down

0 comments on commit 772867a

Please sign in to comment.