attempt_this 0.9.0 → 0.9.1

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.
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