Writing contingent Ruby code with #retryable
2010/02/08 // Leeds // // Feed
Sometimes when writing a pivotal piece of code that’s prone to raising errors, you want to be able to rescue and recover easily. I needed just that when I was programming some code that needed to be reliable when communicating through sockets.
I found this really useful post on creating a retryable block of code. I decided to better it by adding the ability to use multiple exception classes. Here’s a really nice way of retrying a chunk of code n times:
def retryable(options={}, &block)
attempts = options[:attempts] || 1
exception_classes = [*options[:on] || StandardError]
begin
return yield
rescue *exception_classes
retry if (attempts -= 1) > 0
end
yield
end
Use it like this:
retryable(:on => [MyExceptionClass, StandardError], :attempts => 5) do
# .. my risky code ..
end
This is great but it didn’t feel as elegant as it could have been. I then stumbled across this post which presented the style I was looking for. Refactored, the above snippet now looks like this:
class Integer
def tries(options={}, &block)
attempts = self
exception_classes = [*options[:on] || StandardError]
begin
return yield
rescue *exception_classes
retry if (attempts -= 1) > 0
end
yield
end
end
Which provides a cleaner call to the previous method:
3.tries :on => NoMethodError do
# ... my risky code ...
end
This provided me a really clean way of catching socket timeout errors and service unavailable calls without repeated begin rescue blocks. Of course you should always provide an error class to rescue from as rescuing from StandardError will just catch any old error.