Skip to content

Commit

Permalink
Cleanup and add regression spec
Browse files Browse the repository at this point in the history
  • Loading branch information
nfedyashev committed Jan 30, 2015
1 parent 0f8b016 commit 3dbe02d
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 151 deletions.
182 changes: 70 additions & 112 deletions lib/retryable.rb
@@ -1,135 +1,93 @@
require 'retryable/version'
require 'retryable/config'
require 'retryable/configuration'

module Retryable
# Used to set up and modify settings for the retryable.
class Configuration
OPTIONS = [
:ensure,
:exception_cb,
:matching,
:on,
:sleep,
:tries
].freeze

attr_accessor :ensure
attr_accessor :exception_cb
attr_accessor :matching
attr_accessor :on
attr_accessor :sleep
attr_accessor :tries

#alias_method :test_mode?, :test_mode

def initialize
@ensure = Proc.new {}
@exception_cb = Proc.new {}
@matching = /.*/
@on = StandardError
@sleep = 1
@tries = 2
end
class << self
# A Retryable configuration object. Must act like a hash and return sensible
# values for all Retryable configuration options. See Retryable::Configuration.
attr_writer :configuration

# Allows config options to be read like a hash
# Call this method to modify defaults in your initializers.
#
# @param [Symbol] option Key for a given attribute
def [](option)
send(option)
# @example
# Retryable.configure do |config|
# config.ensure = Proc.new {}
# config.exception_cb = Proc.new {}
# config.matching = /.*/
# config.on = StandardError
# config.sleep = 1
# config.tries = 2
# end
def configure
yield(configuration)
end

# Returns a hash of all configurable options
def to_hash
OPTIONS.inject({}) do |hash, option|
hash[option.to_sym] = self.send(option)
hash
end
# The configuration object.
# @see Retryable.configure
def configuration
@configuration ||= Configuration.new
end

# Returns a hash of all configurable options merged with +hash+
#
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
def merge(hash)
to_hash.merge(hash)
def enabled?
configuration.enabled?
end
end
end

module Retryable
# Call this method to modify defaults in your initializers.
#
# @example
# Retryable.configure do |config|
# config.ensure = Proc.new {}
# config.exception_cb = Proc.new {}
# config.matching = /.*/
# config.on = StandardError
# config.sleep = 1
# config.tries = 2
# end
def self.configure
yield(configuration)
end
def enable
configuration.enable
end

# The configuration object.
# @see Retryable.configure
def self.configuration
@configuration ||= Configuration.new
end
def disable
configuration.disable
end

class << self
# A Retryable configuration object. Must act like a hash and return sensible
# values for all Retryable configuration options. See Retryable::Configuration.
attr_writer :configuration
end
def retryable(options = {}, &block)
opts = {
:tries => self.configuration.tries,
:sleep => self.configuration.sleep,
:on => self.configuration.on,
:matching => self.configuration.matching,
:ensure => self.configuration.ensure,
:exception_cb => self.configuration.exception_cb
}

check_for_invalid_options(options, opts)
opts.merge!(options)

return if opts[:tries] == 0

on_exception, tries = [ opts[:on] ].flatten, opts[:tries]
retries = 0
retry_exception = nil

def self.retryable(options = {}, &block)
opts = {
:tries => self.configuration.tries,
:sleep => self.configuration.sleep,
:on => self.configuration.on,
:matching => self.configuration.matching,
:ensure => self.configuration.ensure,
:exception_cb => self.configuration.exception_cb
}

check_for_invalid_options(options, opts)
opts.merge!(options)

return if opts[:tries] == 0

on_exception, tries = [ opts[:on] ].flatten, opts[:tries]
retries = 0
retry_exception = nil

begin
return yield retries, retry_exception
rescue *on_exception => exception
raise unless Retryable.enabled?
raise unless exception.message =~ opts[:matching]
raise if retries+1 >= tries

# Interrupt Exception could be raised while sleeping
begin
Kernel.sleep opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep]
rescue *on_exception
return yield retries, retry_exception
rescue *on_exception => exception
raise unless configuration.enabled?
raise unless exception.message =~ opts[:matching]
raise if retries+1 >= tries

# Interrupt Exception could be raised while sleeping
begin
Kernel.sleep opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep]
rescue *on_exception
end

retries += 1
retry_exception = exception
opts[:exception_cb].call(retry_exception)
retry
ensure
opts[:ensure].call(retries)
end

retries += 1
retry_exception = exception
opts[:exception_cb].call(retry_exception)
retry
ensure
opts[:ensure].call(retries)
end
end

private
private

def self.check_for_invalid_options(custom_options, default_options)
invalid_options = default_options.merge(custom_options).keys - default_options.keys
def check_for_invalid_options(custom_options, default_options)
invalid_options = default_options.merge(custom_options).keys - default_options.keys

raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty?
raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty?
end
end
end

16 changes: 0 additions & 16 deletions lib/retryable/config.rb

This file was deleted.

65 changes: 65 additions & 0 deletions lib/retryable/configuration.rb
@@ -0,0 +1,65 @@
module Retryable
# Used to set up and modify settings for the retryable.
class Configuration
OPTIONS = [
:ensure,
:exception_cb,
:matching,
:on,
:sleep,
:tries
].freeze

attr_accessor :ensure
attr_accessor :exception_cb
attr_accessor :matching
attr_accessor :on
attr_accessor :sleep
attr_accessor :tries

attr_accessor :enabled

alias_method :enabled?, :enabled

def initialize
@ensure = Proc.new {}
@exception_cb = Proc.new {}
@matching = /.*/
@on = StandardError
@sleep = 1
@tries = 2

@enabled = true
end

def enable
@enabled = true
end

def disable
@enabled = false
end

# Allows config options to be read like a hash
#
# @param [Symbol] option Key for a given attribute
def [](option)
send(option)
end

# Returns a hash of all configurable options
def to_hash
OPTIONS.inject({}) do |hash, option|
hash[option.to_sym] = self.send(option)
hash
end
end

# Returns a hash of all configurable options merged with +hash+
#
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
def merge(hash)
to_hash.merge(hash)
end
end
end
23 changes: 0 additions & 23 deletions spec/lib/config_spec.rb

This file was deleted.

39 changes: 39 additions & 0 deletions spec/lib/configuration_spec.rb
@@ -0,0 +1,39 @@
require 'spec_helper'

RSpec.describe Retryable do
it 'is enabled by default' do
expect(Retryable).to be_enabled
end

it 'could be disabled' do
Retryable.disable
expect(Retryable).not_to be_enabled
end

context 'when disabled' do
before do
Retryable.disable
end

it 'could be re-enabled' do
Retryable.enable
expect(Retryable).to be_enabled
end
end

context 'when configured globally with custom sleep parameter' do
it 'passes retry count and exception on retry' do
expect(Kernel).to receive(:sleep).once.with(3)

Retryable.configure do |config|
config.sleep = 3
end

count_retryable(:tries => 2) do |tries, ex|
expect(ex.class).to eq(StandardError) if tries > 0
raise StandardError if tries < 1
end
expect(@try_count).to eq(2)
end
end
end
10 changes: 10 additions & 0 deletions spec/spec_helper.rb
Expand Up @@ -4,11 +4,21 @@
RSpec.configure do |config|
config.disable_monkey_patching!

config.before(:each) do
reset_config
end

def count_retryable(*opts)
@try_count = 0
return Retryable.retryable(*opts) do |*args|
@try_count += 1
yield *args
end
end

private

def reset_config
Retryable.configuration = nil
end
end

0 comments on commit 3dbe02d

Please sign in to comment.