How many times you had to deal with the external API's errors in order to make your application behaves properly? You may end up then with the following code, especially when errors classes are generic:

class SomeAPIService
  def self.call
    perform_action
  rescue SomeAPI::Error => e
    if e.message.match(/account expired/)
      send_email_with_notification_about_expired_account
    elsif e.message.match(/api key invalid/)
      send_email_with_notification_about_invalid_api_key
    elsif e.message.match(/unauthorized action performed/)
      Rollbar.error('log some useful info')
    else
      raise(e)
    end
  end
end

This code is far from being readable, easy-testable and perfect, isn't it? If the external API does not provide custom error messages, we can create them on our side:

class SomeAPIAccountExpired < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/account expired/)
  end
end

class SomeAPIInvalidAPIKey < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/api key invalid/)
  end
end

class SomeAPIUnauthorizedAction < StandardError
  def self.===(exception)
    exception.class == SomeAPI::Error && exception.message.match(/unauthorized action performed/)
  end
end

Want to receive useful tips, information about new Ruby gems and articles on a daily basis? Make sure you follow me and say hello!

How does it work? When an error is raised, Ruby is checking the error class against the classes you have defined in the rescue. If there is a match then the exception gets rescued. Every operator in Ruby is a method so we can define our version of === as well.

Having the above error classes defined, allows us to refactor our code in the following way:

class SomeAPIService
  def self.call
    perform_action
  rescue SomeAPIAccountExpired
    send_email_with_notification_about_expired_account
  rescue SomeAPIInvalidAPIKey
    send_email_with_notification_about_invalid_api_key
  rescue SomeAPIUnauthorizedAction
    Rollbar.error('log some useful info')
  end
end

Now, we can test each error class separately and easily test the SomeAPIService. The whole service's code is also readable. We also don't have to re-raise the error when we don't support error message because it happens automatically as such error is not matched. That's it!

Want to become a better Rails developer?
Download for free the Introduction Rails patterns book and dive into the world of refactoring and easy-testable Ruby code today.
Join over 1,000 developers already subscribed to my newsletter and download the book. You can unsubscribe anytime:

Subscribe and get the book!