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.

Other (possibly related) posts

Comments