Skip to content
PLAY VIDEO PLAY VIDEO PLAY VIDEO
By Yuji Yokoo

Exceptional error handling in Ruby

I recently come across some surprising code involving exceptions which prompted me to look a bit deeper into exceptions in Ruby. In this post, I would like to share some of what I found.

Exception vs StandardError

This is where it all started. I had a script that did something like the following (it was not quite this simple, but it shows the important parts):

class MyLib
  class MyLibBaseError < Exception ; end
  class MyIOError < MyLibBaseError ; end

  def do_something
    raise MyIOError
  rescue
    puts "logging error: #{$!.message}" # $! is the last exception raised
  end
end

Calling do_something results in this:

> MyLib.new.do_something
MyLib::MyIOError: MyLib::MyIOError
    from (irb):70:in `do_something'
    from (irb):76
    from /Users/yuji/.rvm/rubies/ruby-2.1.7/bin/irb:11:in `<main>'

This was surprising to me: MyIOError is clearly raised, but puts is not called. This is because the blank rescue only catches StandardError and its subclasses, hence, any exception that is not a subclass of StandardError will not be caught by it.

The built-in exceptions are listed in the Ruby documentation for the Exception class.

Possible options for making this catch MyIOError include:

  • Making MyLibBaseError inherit from StandardError.
  • Modifying the rescue statement to be more specific about which exception classes to rescue from.

I did both of these, since I wanted MyIOError to be caught in any blank rescue statements around the app. And I also feel that using a blank rescue is not good practice, since I do not want to accidentally catch unintended exceptions.

Begin-rescue-end and def-rescue-end

Typically, rescue is used in the form of begin-rescue-end:

begin
  do_something
rescue => e
  do_something_with_error(e)
end

In addition to this, Ruby also has def-rescue-end in method definitions, for when you want to rescue from the entirety of a method:

def do_rescue
  do_something
rescue => e
  do_something_with_error(e)
end

This is a nicer equivalent to:

def do_rescue
  begin
    do_something
  rescue => e
    do_something_with_error(e)
  end
end

Default exception and optional arguments

By default, raise raises RuntimeError. If a string is passed to raise, it will raise a RuntimeError with the given string as its message.

> raise "message!"
RuntimeError: message!

In addition, optional arguments can be passed to raise:

  • With 2 arguments, they will be the exception class to raise and the message.
  • When 3 arguments are given, the first argument is the exception class, the second is the message, and the last one is an array of callback information.
> raise ZeroDivisionError, 'cannot divide by zero!', caller
ZeroDivisionError: cannot divide by zero!
    from /Users/yuji/.rvm/rubies/ruby-2.1.7/lib/ruby/2.1.0/irb/workspace.rb:86:in `eval'
    from /Users/yuji/.rvm/rubies/ruby-2.1.7/lib/ruby/2.1.0/irb/workspace.rb:86:in `evaluate'
  ...

Multiple rescues

Multiple rescue parts can be used to rescue different exceptions:

def multi_rescue
  1 / 0
rescue NameError
  puts "NameError raised."
rescue ZeroDivisionError
  puts "ZeroDivisionError raised."
end

If they are handled exactly the same, they can be rescued together:

def multi_rescue
  1 / 0
rescue NameError, ZeroDivisionError => e
  puts "#{e.class.name} raised. "
end
> multi_rescue
ZeroDivisionError raised.
 => nil

Ensure

Regardless of whether an exception is rescued, the code in the ensure section will be executed. This is useful, for example, to close a file that has been opened.

def read_file
  f = File.open('input_file.txt', 'r')
  # ... do something
rescue
  # error processing
  puts 'error handled'
ensure
  f.close
  puts 'closed file'
end

When running this with an error:

> read_file
error handled
closed file
  => nil

And without an error:

> read_file
closed file
  => nil

Else

This is not very common, but else can be used to execute code when nothing is raised. This is a little like else in Ruby's case-when-else.

def use_else
  puts "doing something"
rescue NameError
  puts "NameError raised."
rescue ZeroDivisionError
  puts "ZeroDivisionError raised."
else
  puts "nothing raised"
ensure
  puts "ensuring."
end
> use_else
doing something
nothing raised
ensuring.
 => nil

Raise and fail

In Ruby, raise and fail do the same. You can raise an exception or fail with an exception. raise seems to be more commonly used, but it is really up to you to decide which one expresses your intentions better.

> raise 'raise!'
RuntimeError: raise!
    from (irb):19
    from /Users/yuji/.rvm/rubies/ruby-2.1.7/bin/irb:11:in `<main>'
> fail 'fail!'
RuntimeError: fail!
    from (irb):20
    from /Users/yuji/.rvm/rubies/ruby-2.1.7/bin/irb:11:in `<main>'

One-liner

Rescue can be a one-liner:

> puts 1/0 rescue nil # ZeroDivisionError is suppressed here.
 => nil

This makes it really easy to ignore errors, though it is not possible to specify which exceptions to handle here. So it will catch StandardError and all its subclasses. I would not particularly recommend this, since I feel it is too easy to make mistakes by overusing this.

Ignoring exceptions

Rescuing and not doing anything with the caught exception is a common anti-pattern:

def ignore_error
   do_something
rescue
end

Things like this often cause problems in tracking down issues later on. I advise against this, and would suggest at least logging the error and re-raising the same exception, or, if you have to, adding a comment to the code so your colleagues know that the exception has intentionally been ignored.

Catch and throw

Although this is not a use of Exception, Ruby also has catch and throw constructs. This may be useful for people who are used to try-catch in other languages. It can also be used to jump out of loops.

def catch_foo
  catch :foo do
    puts "start"
    i = 0
    while i < 10
      j = 0
      while j < 10
        puts "i: #{i}, j: #{j}"
        throw :foo
        j += 1
      end
      i += 1
    end
    puts "end"
  end
  puts "all done!"
end
> catch_foo
start
i: 0, j: 0
all done!
 => nil

An uncaught throw will result in an ArgumentError:

> throw :foo
ArgumentError: uncaught throw :foo

Final Thoughts

Even though there are many different ways to write exception handling code, I would generally stick to basic, simple rules:

  • Inherit from StandardError when defining your own exception classes.
  • Specify which exceptions types to rescue.
  • Use ensure to execute code regardless of whether or not there was an exception.
  • Use many rescue sections for different errors, and else for when nothing is raised.
  • Do not use a one-liner rescue.
  • Do not simply ignore or suppress exceptions.

However, I myself may not always follow all of these rules; exception handling, by definition, is dealing with exceptional situations, and there may be situations where it makes sense not to follow the rules!

Latest Articles by Our Team

Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.

We Hire Only the Best

reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.

We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.

Free Community Workshops

We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.

  • Webinars

    Webinars

    Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.

    Learn more about webinars

  • Installfest

    Installfest

    The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.

    Learn more about Installfest

  • Development Hub

    Development Hub

    The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.

    Learn more about Development Hub

Get the “reinteractive Review” Monthly Email