When you use a rescue clause in Ruby, you can specify what kinds of exceptions you want to rescue. All you need to do is provide a list of exception classes like so:

begin
  raise RuntimeError
rescue RuntimeError, NoMethodError
  puts "rescued!"
end

But what if you don't know what the exception class will be at the time that you write the code? The most obvious answer is to rescue all exceptions, perform some kind of test and then re-raise the exceptions that don't pass. Something like this:

begin
  raise "FUBAR! The ship's going down!"
rescue => e
  raise unless e.message =~ /^FUBAR/
  ... do something ...
end

But that's so boring! Plus, it's not a very DRY approach. It would be a lot more interesting if we could somehow tell the rescue clause to only rescue exceptions matching our conditions. And since this is Ruby, we can do it!

How rescue matches exceptions

When an exception happens inside of a rescue block, the ruby interpreter checks the exception's class against the list of exception classes you provided. If there's a match, the exception gets rescued.

The matching looks something like this:

exception_classes_to_rescue.any? do |c|
  c === raised_exception.class
end

Just like every other operator in Ruby, === is simply a method. In this case it's a method of c. So what could we do if we defined our own === method?

In the example below I'm creating a class named Anything where  Anything === x  returns  true for any value of x. If I give this class as an argument to rescue, it causes all exceptions to be rescued. 

class Anything
  def self.===(exception)
    true
  end
end

begin
  raise EOFError
rescue Anything
  puts "It rescues ANYTHING!"
end

While there are much better ways to rescue all exceptions, this code is interesting because it shows us two things:

  1. You can give the rescue clause classes that don't inherit from Exception, as long as they implement ===

  2. If you control ===, you can control which exceptions are rescued.

Rescuing exceptions based on message

Knowing what we know now, it's simple to write code that only rescues exceptions if the exception's message matches a pattern.

class AllFoobarErrors
  def self.===(exception)
    # rescue all exceptions with messages starting with FOOBAR 
    exception.message =~ /^FOOBAR/
  end
end

begin
  raise EOFError, "FOOBAR: there was an eof!"
rescue AllFoobarErrors
  puts "rescued!"
end

Rescuing exceptions based on custom attributes

Since you have access to the exception object, your matcher can use any data contained inside that object.

Imagine for a moment that you have an exception that has a custom attribute called "severity." You'd like to swallow all "low severity" occurrences of the exception, but let pass any "high severity" occurrences. You might implement that like so:

class Infraction < StandardError
  attr_reader :severity
  def initialize(severity)
    @severity = severity
  end
end

class LowSeverityInfractions
  def self.===(exception)
    exception.is_a?(Infraction) && exception.severity == :low
  end
end

begin
  raise Infraction.new(:low)
rescue LowSeverityInfractions
  puts "rescued!"
end

Making it dynamic

All of this is pretty cool, but it does involve a lot of boilerplate code. It seems excessive to have to manually define separate classes for each matcher. Fortunately we can DRY this up quite a bit by using a little metaprogramming.

In the example below, we're defining a method that generates matcher classes for us. You provide the matching logic via a block, and the matching generator creates a new class that uses the block inside of its === method.

def exceptions_matching(&block)
  Class.new do
    def self.===(other)
      @block.call(other)
    end
  end.tap do |c|
    c.instance_variable_set(:@block, block)
  end
end

begin
  raise "FOOBAR: We're all doomed!"
rescue exceptions_matching { |e| e.message =~ /^FOOBAR/ }
  puts "rescued!"
end

A grain of salt

Like many cool tricks in Ruby, I can't quite decide if all of this is insanity or a great idea. Maybe it's a little of both. While I definitely wouldn't suggest that you reach for this technique as a first choice, I can see how it would be useful in situations like the one above where you want to rescue exceptions based on severity. In any case, it's another tool in your toolbelt!