cult3

Using Ruby Exceptions

Jul 22, 2015

Table of contents:

  1. What is an Exception
  2. Using Exceptions in Ruby
  3. The different types of Exceptions
  4. Rescuing an Exception
  5. Rescuing different types of Exception
  6. Defining your own Exceptions
  7. Conclusion

When developing an application, it’s easy to just think about the happy path. The happy path is the flow through the application when everything works as expected.

The user enters the correct data, the request satisfies the business logic and any interaction with third party services or infrastructure happens without a hitch.

In reality, there are many more paths than the happy path.

Exceptions are a way of dealing with exceptional circumstances that will occur and they are a very important tool for controlling the execution flow of your application.

In today’s tutorial we will be looking at using Exceptions in our Ruby applications.

What is an Exception

An Exception is used to halt the execution of the application under a particular set of circumstances.

The Exception can then be “rescued” and either dealt with, or allowed to bubble up to the surface.

When an Exception is rescued, the problem can sometimes be resolved.

However, the majority of the time you would want to rescue the exception and provide a good error message to the user.

Exceptions can also be used stop execution if you think the user has malicious intent.

Using Exceptions in Ruby

An Exception is basically just a special type of Ruby object that when created will terminate the current application.

All Exceptions inherit from the standard Exception Ruby object.

To create new Exception you use the raise method:

def oh_noes
  raise 'oh noes, something went wrong!!1'
end

oh_noes

The raise method accepts a message that can be used to help figure out what went wrong.

The code above would result in the following error:

# exceptions.rb:2:in `oh_noes': oh noes, something went wrong!!1 (RuntimeError) from exceptions.rb:5:in `<main>'

So as you can see, Ruby tells us:

  • The line in which the Exception was thrown exceptions.rb:2
  • The method name oh_noes
  • The error message something went wrong!!1
  • The type of Exception (RuntimeError)
  • And where the method was triggered from exceptions.rb:5

The different types of Exceptions

An important aspect of using Exceptions is using the correct Exception under the specific circumstances.

As you saw above, by default when you call the raise method, an instance of RuntimeError will be thrown.

However, Ruby has a number of other default Exceptions that should be used under the appropriate circumstances. You can see a list of these exceptions in the Exception documentation.

If you want to raise a specific type of error, you can pass it as the first argument to the raise method:

def oh_noes
  raise ArgumentError, 'oh noes, something went wrong!!1'
end

This will raise an Exception of type ArgumentError which might be more appropriate if a method was accepting a specific type of argument in order to function correctly.

Rescuing an Exception

When an Exception is raised, you will often want to rescue the situation and take a different course of action or provide a more informative error for the user.

In order to do this, you can “rescue” the Exception:

def oh_noes
  puts 'Before the Exception'
  begin
    puts 'Just before the Exception'
    raise ArgumentError, 'oh noes, something went wrong!!1'
    puts 'Just after the Exception'
  rescue StandardError
    puts 'Rescuing the Exception'
  end
  puts 'After the Exception'
end

oh_noes

To rescue from an Exception you can wrap the Exception in an begin rescue end block.

The begin section is the processing that might trigger the Exception, and the rescue section is what you want to happen if something triggers the Exception.

As you can see in the method above, we are printing a string to the screen at different stages of this method. If you run this method you will see the following output:

Before the Exception
Just before the Exception
Rescuing the Exception
After the Exception

So as you can see, the Exception is raised and rescued. However, the line just after the Exception is raised is never executed. When an Exception is raised it immediately jumps out and is either rescued or bubbles up to the surface. Any code within that begin block after the Exception will not be run.

Rescuing different types of Exception

It is often the case that running a particular process in your application could potentially cause a few different types of Exceptions that should be handled in different ways.

To handle different types of Exceptions you can use multiple rescue blocks.

For example, take a look at this method that raises two different types of Exception:

def rando
  case rand(1..10)
  when 1..5
    raise 'This is a RuntimeError'
  when 6..10
    raise ArgumentError, 'This is an ArgumentError'
  end
end

If the random number is between 1 and 5 a RuntimeError will be raised, and if the number if between 6 and 10 an ArgumentError will be raised.

We can rescue these different types of Exception using multiple rescue blocks:

begin
  rando
rescue RuntimeError
  puts 'We rescued a RuntimeError'
rescue ArgumentError
  puts 'We rescued an Argument Error'
end

If you run this code you will see the rescue message randomly returned depending on which Exception is raised.

If you want to get the message that is provided when the Exception is raised you can do so like this:

begin
  rando
rescue RuntimeError => e
  puts e.message
rescue ArgumentError => e
  puts e.message
end

This will turn the Exception into an object (e). You can then call the message method to get the message from the Exception.

Typically the message of the Exception will contain a human readable explanation of what went wrong.

This can be useful if you need to return an error message, but you are not entirely sure what caused the exception at runtime.

Defining your own Exceptions

Ruby provides a number of standard Exceptions that allow you to signify the different exceptional circumstances an application can find itself in.

However, using Exceptions is much more powerful than just dealing with common application problems.

Exceptions allow you to expressively deal with “exceptional” circumstances, no matter what those circumstances are.

By defining your own Exceptions, you can write code that is very explicit when something goes wrong.

To define your own Exception, you can simply create a new class that inherits from one of the standard Ruby Exception classes:

class UserDoesNotHavePermission < StandardError
end

In this example we have created a new UserDoesNotHavePermission Exception that should be used when the user is attempting to perform an action they do not have permission for. As you can see, the cause of the Exception is immediately obvious.

Now when writing you code, you can throw this specific Exception under that specific circumstance.

This is beneficial for two reasons.

Firstly, if another developer is consuming you code and they receive an instance of the this Exception, it is immediately obvious what went wrong.

Secondly, when testing your code, you can very easily assert that the code failed for the correct reason by ensuring that the correct Exception is raised, and not just a StandardError which could of be raised for any number of reasons.

A couple of weeks ago we looked at using Modules (Creating and using Modules in Ruby). Modules are also a really good way of grouping Exceptions:

module UserPermissions
  class PermissionError < RuntimeError
  end

  class UserDoesNotHavePermission < PermissionError
  end

  class UserDoesNotBelongToAccount < PermissionError
  end

  class UserIsNotAnAdmin < PermissionError
  end
end

In this example we’ve defined a new UserPermissions module that has 4 Exceptions.

To handle a specific exception, you can rescue that particular Exception:

UserPermissions::UserIsNotAnAdmin

However, you can also rescue any of the Exceptions from this module by using the generic PermissionError Exception:

UserPermissions::PermissionError

Conclusion

Exceptions are a beautiful way of dealing with problems within your code. There will be inevitably any number of different paths and situations your code can find itself in, and so using Exceptions is an expressive and explicit way to deal with these different circumstances.

Exceptions make your code easier to understand and deal with problems. When another developer is using your code, you can make it really easy for them to know what went wrong by providing granular Exceptions that can be dealt with in different ways.

It’s also much easier to test code and assert that the code failed for the correct reason. When you create explicit Exceptions you can assert that what you think happened really did happen, rather than simply listening out for generic Exceptions.

There is actually a lot more to Exceptions than what we have covered today. Exceptions can be used in a number of interesting circumstances to deal with problems. However, instead of trying to bite off more than we can chew, I think it’s probably better to look at those interesting situations in isolation. No doubt we will be using Exceptions a lot in future tutorials.

Of course, Exceptions can be used or misused under the wrong circumstances. We haven’t really looked at when you should and should not use Exceptions in today’s tutorial.

If you are new to the idea of using Exceptions, I would get your feet wet with using them first, before you get into the nuances of when you should and should not be using them.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.