Exception Causes in Ruby 2.1

Sometimes when rescuing an exception in Ruby, it’s useful to handle the
error scenario by raising another, different exception. As an example, we may
want to add domain-specific failure information before passing the error on to
client code.

Exceptional Ruby book

The trouble with this technique is that it throws away all the information
held by the original exception. This makes debugging harder, as there’s no
stack trace to follow back to the root cause of the failure.

In Exceptional Ruby I demonstrated
how to write nested
exceptions
in Ruby. A nested exception carries an optional field
pointing back to an “original” exception. Here’s the example from the book:

The implementation of MyError uses a slightly sneaky trick. The
initializer uses $! as the default value for
original. $!, aka $ERROR_INFO, is a
special Ruby variable which always points to the current exception if an
exception is presently being raised. If no exception is being raised, it is
nil.

This is why, in the example above, we’re able to raise MyError
in the usual way, without explicitly initializing it with an
original error. By defaulting to $!, the
MyError initializer automatically picks up original
from the environment.

Until today, a user-defined nested exception class such as this one was the
only way to capture and retain information about an original exception that
triggered a secondary exception. But today Ruby 2.1 dropped. One of the new
features is a new method #cause on the base Exception
class. #cause is automatically filled-in by raise (or
fail), based on the value of $! just like our
MyError implementation.

Let’s try it out:

This is just like our first example, except there’s no need for a special
exception class, and we’ve changed .original to
.cause.

Unlike MyError, there is no way to explicitly set
Exception#cause. It is always implicitly filled in based on the
environment it is raised in.

I’m pretty thrilled that this feature has finally made it into Ruby.
Hand-rolled nested exception classes are useful for debugging, but they don’t
do us any good when trying to debug errors raised in 3rd-party code that
doesn’t use them. With the advent of Exception#cause, debugging
Ruby exceptions just got a lot easier.