Custom Exception Handling in Ruby
Tags: ruby, webdev, programming
Published: 2020-05-19 21:30:14

_📣 This post originally appeared as [Custom Exception Handling in Ruby](https://blog.bearer.sh/handle-ruby-exceptions/) on [The Bearer Blog](https://blog.bearer.sh)._ 
<!--kg-card-begin: markdown-->

In Ruby, like in most languages, an exception is a way to convey that something went wrong. While some languages only use exceptions for truly _exceptional_ circumstances, like run-time errors, Ruby uses exceptions for a wide variety of errors and unexpected results.

In this article, we will look at:

-   How to handle common errors
-   How to react to a specific exception
-   How to create your own custom exceptions

## How to raise and rescue exceptions in Ruby

Your own functions, Ruby, or gems and frameworks can raise exceptions internally. If you are familiar with Javascript of a C-inspired language, you may know of the `try/catch` concept. Ruby has something similar for handling exceptions, using the `begin-end` block. It can contain one or more `rescue` clauses. Let's look at an example:

```ruby
begin
  # Your code
  cause_an_error()
  rescue
    puts "An error has occurred"
end
```

This is one way to build a basic ruby exception handler. This is also a way to catch common errors, as `rescue` without any arguments will catch any [`StandardError`](https://ruby-doc.org/core-2.7.0/StandardError.html) and its subclasses.

You can also assign the exception to a local variable, using `rescue => var_name`, to gain access to properties of the exception. For example:

```rb
# ...
rescue => error
  puts "#{error.class}: #{error.message}"
end
```

We said that `rescue` on its own catches `StandardError` and its subclasses. You can also specify the error type you'd like to target. This allows you to use multiple `rescue` clauses. For example:

```rb

begin
  # do something that raises an exception
  do_something()
  rescue NameError
    puts "A NameError occurred. Did you forget to define a variable?"
  rescue CustomError => error
    puts "A #{error.class} occurred: #{error.message}"
end
```

In this example, the multiple rescue blocks handle each error type separately.

You can also rescue multiple error types at the same time by separating them with a comma. For example:

```rb
# ...
rescue NameError, CustomError, ArgumentError => error
  # handle errors
end
```

In these examples, we use `begin` and `end`. You can also use `rescue` inside a method or block without the need for begin and end. For example:

```rb
def my_function
  perform_action()
  rescue => error
    puts error.message
end
```

## Else and ensure

In addition to `begin-end` and `rescue`, Ruby offers `else` and `ensure`. Let's look at an example:

```rb
begin
  # perform some actions
  rescue => error
    puts "Handle the #{error.class}"
  else
    # Code in the else block will run if no exception occurs
    do_someting_else()
  ensure
    # Code in the ensure block will always run
    always_do_this()
end
```

Any code in the `else` block will run if no exceptions occur. This is like a "success" block. Code in the `ensure` block will run every time, even if the code does not throw an exception.

To summarize:

-   The code in `begin` will try to run.
-   If an error occurs and throws an exception, `rescue` will handle the specified error types.
-   If no error occurs, the `else` block will run.
-   Finally, `ensure`'s block will run _always_.

## Raising specific exceptions

Now that we know how to handle, or rescue, errors and exceptions we can look at how to raise or throw an exception. Every exception your code rescues was _raised_ at some location in the stack.

To raise a `StandardError`:

```rb
raise StandardError.new('Message')
# OR
raise "Message"
```

Since `StandardError` is the default exception type for `raise`, you can omit creating a new instance and instead pass the message on its own.

Let's look at this in the context of a function and a `begin-end` block.

```rb

def isTrue(x)
  if x
    puts "It's true"
  else
    raise StandardError.new("Not a truthy value")
  end
end

begin
  isTrue(false)
  rescue => error
    puts error.message
end
```

Here we have an `isTrue` function that takes an argument (x) and either `puts` if x is true or raises an error if it is false. Then, in the begin block `rescue` ensures that the code recovers from the `raise` within `isTrue`. This is how you will commonly interact with errors. By recovering from a `raise` that occurred in another part of your codebase.

## Creating custom exceptions

If you are building a gem or library, it can be useful to customize the types of errors your code raises. This allows consumers of your code to rescue based on the error type, just as we've done in the examples so far. To create your own exceptions, it is common to make them subclasses of `StandardError`.

```rb
class MyCustomError < StandardError; end
```

Now, you can `raise MyCustomError` when necessary, and your code's consumers can `rescue MyCustomError`.

You can also add properties to custom exception types just as you would any other class. Let's look at an example of a set of errors for a [circuit breaker](https://blog.bearer.sh/circuit-breaker-design-pattern/). The circuit breaker pattern is useful for adding resiliency to API calls. For our purposes, all you need to know is that there are three _states_. Two of them may cause an error.

Let's create a custom error that, instead of just taking an error message, also takes the `state` of our circuit.

```rb
class CircuitError < StandardError
  def initialize(message, state)
    super(message)
    @state = state
  end
  attr_reader :state
end
```

The new `CircuitError` class inherits from `StandardError`, it passes `message` to the parent class and makes `state` accessible to the outside.

Now, if we look at this in the context of a `rescue`, we can see how it might be used.

```rb
begin
  raise CircuitError.new("The circuit breaker is active", "OPEN")
  rescue CircuitError => error
    puts "#{error.class}: #{error.message}. The STATE is: #{error.state}."

    # => CircuitError: The circuit breaker is active. The STATE is: OPEN.
end
```

The rescue block can now take advantage of the added `state` property that exists on the error instance.

## Adding custom exceptions to your modules

If you are developing a module, you can also take this a step further by incorporating custom error types into the module. This allows for better name-spacing and makes it easier to identify where the error is coming from. For example:

```rb
module MyCircuit
  # ...
  module Errors
    class CircuitError < StandardError; end
  end
  # ...
end
```

You can raise the error type as follows:

```rb
raise MyCircuit::Errors::CircuitError.new('Custom error message')
```

It is common for libraries to include a set of subclassed error types. Generally, these live in `exceptions.rb` or `errors.rb` file within the library.

## Rescue exceptions based on the parent class

So far we've seen how to rescue exceptions, raise exceptions, even create our own. One more trick is the ability to recover from errors based on their parent class.

Let us take our circuit breaker error from earlier and split it into one parent and two children. Rather than require the `raise` clauses to pass in arguments, we will handle that in the errors themselves.

```rb
class CircuitError < StandardError
  def initialize(message)
    super(message)
  end
end

class CircuitOPEN < CircuitError
  def initialize
    super('The Circuit Breaker is OPEN')
  end
end

class CircuitHALF < CircuitBreaker
  def initialize
    super('The Circuit Breaker is HALF-OPEN')
  end
end
```

Here, both `CircuitOPEN` and `CircuitHALF` are subclasses of `CircuitError`. This may not seem useful, but it allows us to check for either error individually, or all subclasses of `CircuitError`. Let's see what that looks like.

To `rescue` them individually would look like:

```rb
begin
  # ...
  raise CircuitOPEN

  rescue CircuitHALF
    # do something on HALF error
  rescue CircuitOPEN
    # do something on OPEN error
end
```

We could also group them together:

```rb
rescue CircuitHALF, CircuitOPEN
  # do something on both
```

But even better, since they are both subclasses of `CircuitError`, we can rescue them all by rescuing `CircuitError`.

```rb
begin
  raise CircuitHALF
  # or raise CircuitOPEN
  rescue CircuitError
  # will catch both HALF or OPEN
end
```

By utilizing this method, your code can react to types of errors in addition to specific errors.

## How to manage errors

This approach to handling exceptions in ruby goes a long way toward providing valuable errors to consumers of your code. Not only can you throw and raise exceptions, but your users can now catch exceptions in their ruby code that may come from your library.

At [Bearer](https://bearer.sh), we use an approach similar to this in our Agent codebase to provide our users with error details that help them identify where the problem is coming from.

If you're interested in preventing errors in your codebase, check out what we're building at [Bearer](https://bearer.sh). The Bearer Agent makes handling API inconsistencies easier by offering automatic remediations, resiliency patterns, and notifications with no changes to your code.

Explore more on the [Bearer Blog](https://blog.bearer.sh/) and connect with us [@BearerSH](https://twitter.com/bearersh).

<!--kg-card-end: markdown-->