attempt_this 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,7 +1,3 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
- gem 'rspec'
4
-
5
- group :test do
6
- gem 'simplecov', require: false
7
- end
3
+ gemspec
data/Gemfile.lock CHANGED
@@ -1,8 +1,17 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ attempt_this (0.9.1)
5
+
1
6
  GEM
2
7
  remote: http://rubygems.org/
3
8
  specs:
4
9
  diff-lcs (1.2.4)
5
- multi_json (1.7.7)
10
+ docile (1.1.2)
11
+ macaddr (1.6.1)
12
+ systemu (~> 2.5.0)
13
+ multi_json (1.8.4)
14
+ rake (10.1.1)
6
15
  rspec (2.13.0)
7
16
  rspec-core (~> 2.13.0)
8
17
  rspec-expectations (~> 2.13.0)
@@ -11,15 +20,23 @@ GEM
11
20
  rspec-expectations (2.13.0)
12
21
  diff-lcs (>= 1.1.3, < 2.0)
13
22
  rspec-mocks (2.13.1)
14
- simplecov (0.7.1)
15
- multi_json (~> 1.0)
16
- simplecov-html (~> 0.7.1)
17
- simplecov-html (0.7.1)
23
+ simplecov (0.8.2)
24
+ docile (~> 1.1.0)
25
+ multi_json
26
+ simplecov-html (~> 0.8.0)
27
+ simplecov-html (0.8.0)
28
+ systemu (2.5.2)
29
+ uuid (2.3.7)
30
+ macaddr (~> 1.0)
18
31
 
19
32
  PLATFORMS
20
33
  ruby
21
34
  x86-mingw32
22
35
 
23
36
  DEPENDENCIES
37
+ attempt_this!
38
+ bundler
39
+ rake
24
40
  rspec
25
41
  simplecov
42
+ uuid
data/README.md CHANGED
@@ -5,7 +5,7 @@ Exception-based retry policy mix-in for Ruby project. Its purpose it to retry a
5
5
  ```ruby
6
6
 
7
7
  attempt(3.times) do
8
- # Do something
8
+ # Do something
9
9
  end
10
10
  ```
11
11
  This will retry the code block up to three times. If the last attempt will result in an exception, that exception will be thrown outside of the attempt block.
@@ -14,7 +14,7 @@ If you don't like that behavior, you can specify a function to be called after a
14
14
  is_failed = false
15
15
  attempt(3.times)
16
16
  .and_default_to(->{is_failed = true}) do
17
- # Do something
17
+ # Do something
18
18
  end
19
19
  ```
20
20
 
@@ -22,7 +22,7 @@ You may want to retry on specific exception types:
22
22
  ```ruby
23
23
  attempt(3.times)
24
24
  .with_filter(RecoverableError1, RecoverableError2) do
25
- # Do something
25
+ # Do something
26
26
  end
27
27
  ```
28
28
 
@@ -30,9 +30,9 @@ You may chose how to reset the environment between failed attempts. This is usef
30
30
  ```ruby
31
31
  attempt(3.times)
32
32
  .with_reset(->{rollback}) do
33
- start_transaction
34
- # Do something
35
- commit_transaction
33
+ start_transaction
34
+ # Do something
35
+ commit_transaction
36
36
  end
37
37
  ```
38
38
  You can specify delay between failed attempts:
@@ -42,19 +42,19 @@ You can specify delay between failed attempts:
42
42
  # Wait for 5 seconds between failures.
43
43
  attempt(3.times)
44
44
  .with_delay(5) do
45
- # Do something
45
+ # Do something
46
46
  end
47
47
 
48
48
  # Random delay between 30 and 60 seconds.
49
49
  attempt(3.times)
50
50
  .with_delay([30..60]) do
51
- # Do something
51
+ # Do something
52
52
  end
53
53
 
54
54
  # Start with 10 seconds delay and double it after each failed attempt.
55
55
  attempt(5.times)
56
56
  .with_binary_backoff(10) do
57
- # Do something
57
+ # Do something
58
58
  end
59
59
  ```
60
60
 
@@ -77,8 +77,11 @@ AttemptThis.attempt(5.times).with_filter(*RECOVERABLE_HTTP_ERRORS).scenario(:htt
77
77
 
78
78
  # And run this from your method:
79
79
  attempt(:http) do
80
- # Make an HTTP call
80
+ # Make an HTTP call
81
81
  end
82
82
  ```
83
83
 
84
+ # Return values
85
+ Calling 'attempt' will return result of the code block if there was no exception; otherwise it will return result of the default handler.
86
+
84
87
  Enjoy! And feel free to contribute; just make sure you haven't broken any tests by running 'rake' from project's root.
data/attempt_this.gemspec CHANGED
@@ -1,15 +1,22 @@
1
1
  Gem::Specification.new do |s|
2
- s.name = 'attempt_this'
3
- s.version = '0.9.0'
4
- s.date = '2013-05-05'
5
- s.summary = 'Retry policy mix-in'
6
- s.description = <<EOM
2
+ s.name = 'attempt_this'
3
+ s.version = '0.9.1'
4
+ s.date = '2014-02-18'
5
+ s.summary = 'Retry policy mix-in'
6
+ s.description = <<EOM
7
7
  Retry policy mix-in with configurable number of attempts, delays, exception filters, and fall back strategies.
8
8
 
9
9
  See project's home page for usage examples and more information.
10
10
  EOM
11
- s.authors = ['Aliaksei Baturytski']
12
- s.email = 'abaturytski@gmail.com'
13
- s.files = `git ls-files`.split($/)
14
- s.homepage = 'https://github.com/aliakb/attempt_this'
11
+ s.authors = ['Aliaksei Baturytski']
12
+ s.email = 'abaturytski@gmail.com'
13
+ s.files = `git ls-files`.split($/)
14
+ s.homepage = 'https://github.com/aliakb/attempt_this'
15
+ s.license = 'MIT'
16
+
17
+ s.add_development_dependency('bundler')
18
+ s.add_development_dependency('rake')
19
+ s.add_development_dependency('rspec')
20
+ s.add_development_dependency('simplecov')
21
+ s.add_development_dependency('uuid')
15
22
  end
@@ -2,133 +2,125 @@ require_relative 'binary_backoff_policy.rb'
2
2
  require_relative 'exception_type_filter.rb'
3
3
 
4
4
  module AttemptThis
5
- # Retry policy implementation.
6
- # This class is internal and is not supposed to be used outside of the module.
7
- class AttemptObject
8
- @@scenarios = {} # All registered scenarios
9
-
10
- # Resets all static data.
11
- def self.reset
12
- @@scenarios = {}
13
- end
14
-
15
- def self.get_object(id_or_enumerator)
16
- impl = @@scenarios[id_or_enumerator]
17
- impl ||= AttemptObject.new(id_or_enumerator)
18
- impl
19
- end
20
-
21
- # Initializes object with enumerator.
22
- def initialize(enumerator)
23
- @enumerator = enumerator
24
- end
25
-
26
- # Executes the code block.
27
- def attempt(block)
28
- if (block)
29
- last_exception = nil
30
- first_time = true
31
-
32
- @delay_policy = ->{} unless @delay_policy
33
- @reset_method = ->{} unless @reset_method
34
- @exception_filter = ExceptionTypeFilter.new([StandardError]) unless @exception_filter
35
-
36
- @enumerator.rewind
37
- @enumerator.each do
38
- @delay_policy.call unless first_time
39
- last_exception = nil
40
- begin
41
- block.call
42
- break
43
- rescue => ex
44
- raise unless @exception_filter.include?(ex)
45
- last_exception = ex
46
- @reset_method.call
47
- end
48
- first_time = false
49
- end
50
-
51
- # Re-raise the last exception
52
- if (last_exception)
53
- if (@default_method)
54
- @default_method.call
55
- else
56
- raise last_exception
57
- end
58
- end
59
- end
60
-
61
- # Return self to allow chaining calls.
62
- self
63
- end
64
-
65
- # Specifies delay in seconds between failed attempts.
66
- def with_delay(delay, &block)
67
- # Delay should be either an integer or a range of integers.
68
- if (delay.is_a?(Numeric))
69
- raise(ArgumentError, "Delay should be a non-negative number; got #{delay}!") unless delay >= 0
70
- delay = delay..delay
71
- elsif delay.is_a?(Range)
72
- raise(ArgumentError, "Range members should be numbers; got #{delay}!") unless delay.first.is_a?(Numeric) && delay.last.is_a?(Numeric)
73
- raise(ArgumentError, "Range members should be non-negative; got #{delay}!") unless delay.first >= 0 && delay.last >= 0
74
- raise(ArgumentError, "Range's end should be greater than or equal to range's start; got #{delay}!") unless delay.first <= delay.last
75
- else
76
- raise(ArgumentError, "Delay should be either an number or a range of numbers; got #{delay}!")
77
- end
78
- raise(ArgumentError, 'Delay policy has already been specified!') if @delay_policy
79
- @delay_policy = ->{Kernel.sleep(rand(delay))}
80
-
81
- attempt(block)
82
- end
83
-
84
- # Specifies reset method that will be called after each failed attempt.
85
- def with_reset(reset_method, &block)
86
- raise(ArgumentError, 'Reset method is nil!') unless reset_method
87
- raise(ArgumentError, 'Reset method has already been speicifed!') if @reset_method
88
-
89
- @reset_method = reset_method
90
- attempt(block)
91
- end
92
-
93
- # Specifies default method that should be called after all attempts have failed.
94
- def and_default_to(default_method, &block)
95
- raise(ArgumentError, 'Default method is nil!') unless default_method
96
- raise(ArgumentError, 'Default method has already been specified!') if @default_method
97
-
98
- @default_method = default_method
99
- attempt(block)
100
- end
101
-
102
- # Specifies delay which doubles between failed attempts.
103
- def with_binary_backoff(initial_delay, &block)
104
- raise(ArgumentError, "Delay should be a number; got ${initial_delay}!") unless initial_delay.is_a?(Numeric)
105
- raise(ArgumentError, "Delay should be a positive number; got #{initial_delay}!") unless initial_delay > 0
106
- raise(ArgumentError, "Delay policy has already been specified!") if @delay_policy
107
-
108
- @delay_policy = BinaryBackoffPolicy.new(initial_delay)
109
- attempt(block)
110
- end
111
-
112
- # Specifies exceptions
113
- def with_filter(*exceptions, &block)
114
- raise(ArgumentError, "Empty exceptions list!") unless exceptions.size > 0
115
- # Everything must be an exception.
116
- exceptions.each do |e|
117
- raise(ArgumentError, "Not an exception: #{e}!") unless e <= Exception
118
- end
119
-
120
- raise(ArgumentError, "Exception filter has already been specified!") if @exception_filter
121
-
122
- @exception_filter = ExceptionTypeFilter.new(exceptions)
123
- attempt(block)
124
- end
125
-
126
- # Creates a scenario with the given id.
127
- def scenario(id)
128
- raise(ArgumentError, 'Blank id!') if id.nil? || id.empty?
129
- raise(ArgumentError, "There is already a scenario with id #{id}") if @@scenarios.has_key?(id)
130
-
131
- @@scenarios[id] = self
132
- end
133
- end
5
+ # Retry policy implementation.
6
+ # This class is internal and is not supposed to be used outside of the module.
7
+ class AttemptObject
8
+ @@scenarios = {} # All registered scenarios
9
+
10
+ # Resets all static data.
11
+ def self.reset
12
+ @@scenarios = {}
13
+ end
14
+
15
+ def self.get_object(id_or_enumerator)
16
+ impl = @@scenarios[id_or_enumerator]
17
+ impl ||= AttemptObject.new(id_or_enumerator)
18
+ impl
19
+ end
20
+
21
+ # Initializes object with enumerator.
22
+ def initialize(enumerator)
23
+ @enumerator = enumerator
24
+ end
25
+
26
+ # Executes the code block.
27
+ def attempt(block)
28
+ # Returning self will allow chaining calls
29
+ return self unless block
30
+
31
+ last_exception = nil
32
+ first_time = true
33
+
34
+ @delay_policy = ->{} unless @delay_policy
35
+ @reset_method = ->{} unless @reset_method
36
+ @exception_filter = ExceptionTypeFilter.new([StandardError]) unless @exception_filter
37
+
38
+ @enumerator.rewind
39
+ @enumerator.with_index do |i|
40
+ @delay_policy.call unless i == 0
41
+ last_exception = nil
42
+ begin
43
+ return block.call
44
+ rescue Exception => ex
45
+ raise unless @exception_filter.include?(ex)
46
+ @reset_method.call
47
+ last_exception = ex
48
+ end
49
+ end
50
+
51
+ if (last_exception)
52
+ return @default_method[] if @default_method
53
+ raise last_exception
54
+ end
55
+ end
56
+
57
+ # Specifies delay in seconds between failed attempts.
58
+ def with_delay(delay, &block)
59
+ # Delay should be either an integer or a range of integers.
60
+ if (delay.is_a?(Numeric))
61
+ raise(ArgumentError, "Delay should be a non-negative number; got #{delay}!") unless delay >= 0
62
+ delay = delay..delay
63
+ elsif delay.is_a?(Range)
64
+ raise(ArgumentError, "Range members should be numbers; got #{delay}!") unless delay.first.is_a?(Numeric) && delay.last.is_a?(Numeric)
65
+ raise(ArgumentError, "Range members should be non-negative; got #{delay}!") unless delay.first >= 0 && delay.last >= 0
66
+ raise(ArgumentError, "Range's end should be greater than or equal to range's start; got #{delay}!") unless delay.first <= delay.last
67
+ else
68
+ raise(ArgumentError, "Delay should be either an number or a range of numbers; got #{delay}!")
69
+ end
70
+ raise(ArgumentError, 'Delay policy has already been specified!') if @delay_policy
71
+ @delay_policy = ->{Kernel.sleep(rand(delay))}
72
+
73
+ attempt(block)
74
+ end
75
+
76
+ # Specifies reset method that will be called after each failed attempt.
77
+ def with_reset(reset_method, &block)
78
+ raise(ArgumentError, 'Reset method is nil!') unless reset_method
79
+ raise(ArgumentError, 'Reset method has already been speicifed!') if @reset_method
80
+
81
+ @reset_method = reset_method
82
+ attempt(block)
83
+ end
84
+
85
+ # Specifies default method that should be called after all attempts have failed.
86
+ def and_default_to(default_method, &block)
87
+ raise(ArgumentError, 'Default method is nil!') unless default_method
88
+ raise(ArgumentError, 'Default method has already been specified!') if @default_method
89
+
90
+ @default_method = default_method
91
+ attempt(block)
92
+ end
93
+
94
+ # Specifies delay which doubles between failed attempts.
95
+ def with_binary_backoff(initial_delay, &block)
96
+ raise(ArgumentError, "Delay should be a number; got ${initial_delay}!") unless initial_delay.is_a?(Numeric)
97
+ raise(ArgumentError, "Delay should be a positive number; got #{initial_delay}!") unless initial_delay > 0
98
+ raise(ArgumentError, "Delay policy has already been specified!") if @delay_policy
99
+
100
+ @delay_policy = BinaryBackoffPolicy.new(initial_delay)
101
+ attempt(block)
102
+ end
103
+
104
+ # Specifies exceptions
105
+ def with_filter(*exceptions, &block)
106
+ raise(ArgumentError, "Empty exceptions list!") unless exceptions.size > 0
107
+ # Everything must be an exception.
108
+ exceptions.each do |e|
109
+ raise(ArgumentError, "Not an exception: #{e}!") unless e <= Exception
110
+ end
111
+
112
+ raise(ArgumentError, "Exception filter has already been specified!") if @exception_filter
113
+
114
+ @exception_filter = ExceptionTypeFilter.new(exceptions)
115
+ attempt(block)
116
+ end
117
+
118
+ # Creates a scenario with the given id.
119
+ def scenario(id)
120
+ raise(ArgumentError, 'Blank id!') if id.nil? || id.empty?
121
+ raise(ArgumentError, "There is already a scenario with id #{id}") if @@scenarios.has_key?(id)
122
+
123
+ @@scenarios[id] = self
124
+ end
125
+ end
134
126
  end
@@ -1,18 +1,18 @@
1
1
  require 'attempt_this/attempt_object.rb'
2
2
 
3
3
  module AttemptThis
4
- extend self
4
+ extend self
5
5
 
6
- # Attempts code block until it doesn't throw an exception or the end of enumerator has been reached.
7
- def attempt(enumerator, &block)
8
- raise(ArgumentError, 'Nil enumerator!') if enumerator.nil?
6
+ # Attempts code block until it doesn't throw an exception or the end of enumerator has been reached.
7
+ def attempt(enumerator, &block)
8
+ raise(ArgumentError, 'Nil enumerator!') if enumerator.nil?
9
9
 
10
- impl = AttemptObject::get_object(enumerator)
11
- impl.attempt(block)
12
- end
10
+ impl = AttemptObject::get_object(enumerator)
11
+ impl.attempt(block)
12
+ end
13
13
 
14
- # Resets all static data (scenarios). This is intended to use by tests only (to reset scenarios)
15
- def self.reset
16
- AttemptObject.reset
17
- end
14
+ # Resets all static data (scenarios). This is intended to use by tests only (to reset scenarios)
15
+ def self.reset
16
+ AttemptObject.reset
17
+ end
18
18
  end