Guide | Ruby

How to Handle Exceptions in Ruby

How to Handle Exceptions in Ruby

Exception Handling in Ruby

In Ruby, error handling works like this; all exceptions and errors are extensions of the Exception class. While this may seem intuitive, exception handling in Ruby is a touch more nuanced than you might expect thanks to the designed hierarchy of Ruby exceptions.

The begin-rescue

Similar to PHP's try-catch handler, Ruby's exception handling begins with the begin-rescue block. In a nutshell, the begin-rescue is a code block in Ruby that can be used to deal with raised exceptions without interrupting the Ruby program execution. In other words, you can begin to execute a block of code, and rescue any exceptions that are raised.

Rescuing Exceptions

In Ruby by default, begin-rescue rescues every instance of the StandardError class. This includes no method errors, type errors, runtime errors, and every custom error that is intended to be rescued within a Ruby application (see Raising Exceptions in Ruby for more information). To rescue every StandardError, simply wrap the designated section of code in a begin-rescue block:

begin
  # ...
rescue => e
  # ...
end

In Ruby when a StandardError exception is raised within the begin block, an instance of it will be passed to the rescue block as the variable e (for more information about the structure of Ruby's Exception class, see Raising Exceptions in Ruby).

Rescuing Specific Exceptions

While rescuing every exception raised in your Ruby app is great for simplistic implementations—such as generalizing API error responses—best practice is to rescue for specific exceptions. To do this, let's rewrite the generic Ruby begin-rescue block above to specifically rescue StandardError exceptions:

begin
  # ...
rescue StandardError => e
  # ...
end

Although the difference may be subtle, by following the rescue command with a class name, then only exceptions of the defined type will be rescued. For example, if we wanted to rescue all argument errors, we could structure our begin-rescue block like this:

begin
  # ...
rescue ArgumentError => e
  # ...
end

But, what if we want to rescue more than one exception type? Much like an if-elsif-else chain, a begin-rescue block can have multiple rescues, which when combined with a check for the StandardError class, allows you to logically adapt to any and all issues that may arise:

begin
  # ...
rescue ArgumentError => e
  # ...
rescue TypeError => e
  # ...
rescue => e
  # ...
end

Rescuing All Exceptions

While it may be tempting to rescue every child of the Exception class, it is generally considered bad practice due to the way the Ruby exception hierarchy is structured. The reason for this is that, while all Ruby exceptions and errors are an extension of the Exception class, many of them are reserved for use internally by Ruby. For example, SignalException::Interrupt is used to signal that Ctrl-C has been detected during the execution of a script.

If you were to rescue every child of the Exception class, then the Interrupt exception wouldn't work as expected. That said, if you do want to rescue every exception that is raised in Ruby, the same begin-rescue block can be used to specifically rescue all Exception exceptions:

begin
  # ...
rescue Exception => e
  # ...
end

Using the above begin-rescue block in Ruby will rescue every exception and error that is raised, from interrupts to syntax errors, and even memory errors, so use it sparingly and with caution.

How to check Ruby syntax to identify exceptions

rescueclauses are used to tell Ruby which exception or types of exceptions we want to handle. The syntax for therescuestatement is:

begin
    # may raise an exception
rescue AnException
    # exception handler
rescue AnotherException
    # exception handler
else
    # other exceptions
ensure
    # always executed
end

The code betweenbeginandrescueis where a Ruby exception can occur. If an exception is encountered, the code inside therescueclause gets executed. For eachrescueclause, the raised Ruby exception is compared against each parameter and the match succeeds if the exception in the clause is the same as or a superclass of the thrown exception.

If the thrown Ruby exception does not match any of the specified exception types, theelse block gets executed. Theensureblock is always executed whether a Ruby exception occurs or not.

As an example:

#!/usr/bin/ruby
begin
   file = open("/tmp/myfile")
rescue Errno::ENOENT
   p "File not found"
else
   p "File opened"
end

In the above example, a file is attempted to be opened in thebeginblock. Therescueblock catches a “File not found” Ruby exception in case the file is not found at the location. If the file is found, theelseblock gets executed.

Running the above Ruby code produces the following result if the file is not found:

"File not found"

If the file is found, the following output is produced:

"File not found"

Exception Handling in Ruby on Rails

Generally speaking, the begin-rescue block works as intended in Ruby on Rails. That said, in order to better handle the specific use cases that can come up in the Rails architecture, additional methods have been made available for use within a Ruby on Rails application.

The rescue_from

The rescue_from directive is an exception handler that rescues exceptions raised in controller actions. The rescue_from directive rescues the specified exceptions raised within a controller, and reacts to those exceptions with a defined method. For example, the following controller rescues User::NotAuthorized exceptions and passes them to the deny_access() method:

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :deny_access

  protected
    def deny_access(exception)
    # ...
    end
end

The advantage to rescue_from is that it abstracts the exception handling away from individual controller actions, and instead makes exception handling a requirement of the controller. This not only makes exception handling within controllers more readable, but also more regimented.

Exception Handling in Sinatra

While the additional exception handling within Ruby on Rails is focused on controller exceptions, Sinatra offers a few additional ways to deal with raised exceptions.

Logging Exceptions with dump_errors

Enabled by default, dump_errors is a Sinatra setting that allows exception backtraces to be written directly to STDERR. In the context of a development server, this information can be incredibly valuable, but might be more difficult to act upon in a production environment. When used in conjunction with traditional log aggregation and analysis techniques, however, this is a great way to collect exception data as it happens without needing to reproduce it.

Propagating Exceptions with raise_errors

By default, exceptions raised within Sinatra do not leave the application. What this means is that, when an exception is raised, it is rescued and mapped to internal error handlers. By enabling the raise_errors setting, these exceptions are raised outside of the application, allowing the server handler or Rack middleware to deal with exceptions.

Enable Classy Error Pages with show_exceptions

When working in a development environment, being able to quickly react to exceptions is crucial, which is why exception backtraces and environment information are on-screen by default in these environments. While this setting is turned off in production environments, it can be enabled or disabled by updating the show_exceptions setting.

Custom Error Handling

While Sinatra has built-in support for graceful error handling (error handler), it is sometimes desirable to write custom logic to handle raised errors. To do this, an error block can be used in the same way as a rescue block. For example, to rescue a User::NotAuthorized error, the following directive would work in Sinatra:

error User::NotAuthorized do
  # ...
end

Similarly, if we wanted to rescue all exceptions that are raised in a Sinatra application, we could use the following error handler:

error do
  # ...
end

Check ruby syntax

If you ever need to check the syntax of your Ruby code here is a great way to do so without executing the Ruby code.

This will check the syntax without executing the program:

ruby -c filename.rb