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
rescue
clauses are used to tell Ruby which exception or types of exceptions we want to handle. The syntax for therescue
statement is:
begin
# may raise an exception
rescue AnException
# exception handler
rescue AnotherException
# exception handler
else
# other exceptions
ensure
# always executed
end
The code betweenbegin
andrescue
is where a Ruby exception can occur. If an exception is encountered, the code inside therescue
clause gets executed. For eachrescue
clause, 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. Theensure
block 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 thebegin
block. Therescue
block catches a “File not found” Ruby exception in case the file is not found at the location. If the file is found, theelse
block 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