Exception Handling in Ruby

Ahmad Hamza

What's an exception?

When you raise an exception in Ruby, the world stops and your program starts to shut down other wise your program will eventually exit with an error message.

Exceptions are Ruby's way of dealing with unexpected events.

eg: SyntaxError or NoMethodError

irb(main):001:0> 1 / 0 
ZeroDivisionError: divided by 0

Just like:

begin
  1/0
rescue
  puts "Got an exception, but I'm responding intelligently!"
  do_something_intelligent()
end


# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"

How to stop this shutdown process, and react to the error intelligently ?

This is called "rescuing," "handling," or "catching" an exception. They all mean the same thing. This is how you do it in Ruby.

The exception still happens but it doesn't cause the program to crash because it was rescued.

Nice, OK.. ?? 

The error message says "Something went  wrong", but it does not let us know what exactly went wrong ?

Exception Objects

To get an exception object, you will use the slightly different syntax.

Exception objects are normal Ruby objects.

# Rescues all errors, an puts the exception object in `e`
rescue => e

# Rescues only ZeroDivisionError and puts the exception object in `e`
rescue ZeroDivisionError => e

ZeroDivisionError is the class of object in 'e'

begin
1 / 0
rescue ZeroDivisionError => e
  puts "Exception Class: #{ e.class.name }"
  puts "Exception Message: #{ e.message }"
  puts "Exception Backtrace: #{ e.backtrace }"
end

# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...

Raising Your Own Exceptions

'raise' - trigger your own exceptions

When you raise, you get to pick which type of exception to use.

You also get to set the error message.

More examples: Gist

def raise_exception  
  puts 'I am before the raise.'  
  raise 'An error has occured'  
  puts 'I am after the raise'  
end

raise_exception

#output
I am before the raise.
RuntimeError: An error has occured
from (irb):3:in `raise_exception'
from (irb):6

raise

The raise method is from the Kernel module.

By default, raise creates an exception of the RuntimeError class.

To raise an exception of a specific class, you can pass in the class name as an argument to raise.

def inverse(x)
  raise ArgumentError, 'Argument is not numeric' unless x.is_a? Numeric
  1.0 / x
end

puts inverse(2)
puts inverse('not a number')

#output
0.5
ArgumentError: Argument is not numeric
	from (irb):2:in `inverse'
	from (irb):7

raise - can be called in several ways

# Explicitly mention the error
raise RuntimeError.new("You messed up!")

# Produces the same result
raise RuntimeError, "You messed up!"

# Produces the same result. But you can only raise 
# RuntimeErrors this way
raise "You messed up!"

Handling an exception

Steps:

  1. Enclose the code that could raise an exception in a begin-end block
  2. Use one or more rescue clauses to tell Ruby the types of exceptions we want to handle.

 Note: The body of a method definition is an implicit begin-end block; the begin is omitted, and the entire body of the method is subject to exception handling, ending with the end of the method.

def raise_and_rescue  
  begin  
    puts 'I am before the raise.'  
    raise 'An error has occured.'  
    puts 'I am after the raise.'  
  rescue  
    puts 'I am rescued.'  
  end  
  puts 'I am after the begin block.'  
end  
raise_and_rescue

# output:
I am before the raise.  
I am rescued.  
I am after the begin block.

Observe that the code interrupted by the exception never gets run. Once the exception is handled, execution continues immediately after the begin block that spawned it.

If you write a rescue clause with no parameter list, the parameter defaults to StandardError.

Each rescue clause can specify multiple exceptions to catch. At the end of each rescue clause you can give Ruby the name of a local variable to receive the matched exception.

begin
  rand(2) == 0 ? ([] + '') : (foo)
rescue TypeError, NameError => e
  puts "oops: #{e.message}"
end

# output:
oops: no implicit conversion of String into Array

You can stack rescue clauses in a begin/rescue block. Exceptions not handled by one rescue clause will trickle down to the next:

begin  
  # -  
rescue OneTypeOfException  
  # -  
rescue AnotherTypeOfException  
  # -  
else  
  # Other exceptions  
end 

ensure

If you need the guarantee that some processing is done at the end of a block of code, regardless of whether an exception was raised then the ensure clause can be used. ensure goes after the last rescue clause and contains a chunk of code that will always be executed as the block terminates.

The ensure block will always run.

The general flow of begin/rescue/else/ensure/end looks like this:

begin

  # something which might raise an exception

rescue SomeExceptionClass => some_variable

  # deals with some exception

rescue SomeOtherException => some_other_variable

  # deals with some other exception

else

  # runs only if *no* exception was raised

ensure

  # ensure that this code always runs, no matter what

end

Making Custom Exceptions

Just create a new class that inherits from StandardError

class PermissionDeniedError < StandardError
end

PermissionDeniedError.new()

Since it is a normal Ruby class, you can add methods and data to it like any other class.

class PermissionDeniedError < StandardError

  attr_reader :action

  def initialize(message, action)
    # Call the parent constructor to set the message
    super(message)

    # Store the action in an instance variable
    @action = action
  end

end


# Then, when the user tries to delete something they do not
# have permission to delete, you might do something like this:

raise PermissionDeniedError.new("Permission Denied", :delete)

Exception class

➜ irb
irb(main):001:0> ArgumentError.superclass
=> StandardError
irb(main):002:0> ArgumentError.superclass.superclass
=> Exception
irb(main):003:0> ArgumentError.superclass.superclass.superclass
=> Object

Exception is the parent class of all other exception classes.

Some useful public methods of Exception class:

  1. backtrace: The backtrace is an array of strings, each containing either "filename:lineNo: in `method" or "filename:lineNo"
  2. message: returns the exception message or name
  3. inspect: Return this exception's class name and message

Exception and its children

Rescuing All Exceptions (the bad way)

// Don't do this 

begin

  do_something()

rescue Exception => e

  ...

end

Don't rescue every exception, it breaks program in weird ways. Because Ruby uses exceptions for things other than errors. It also uses them to handle messages from the operating system called "Signals." If you've ever pressed "ctrl-c" to exit a program, you've used a signal. By suppressing all exceptions, you also suppress those signals.

Rescuing All Errors (the right way)

begin
  do_something()
rescue StandardError => e
  # Only your app exceptions are swallowed. Things like SyntaxErrror are left alone. 
end

if you want to rescue "all errors" you should rescue StandardError.

If you don't specify an exception class, Ruby assumes you mean StandardError

begin
  do_something()
rescue => e
  # This is the same as rescuing StandardError
end

Rescuing Specific Errors (the best way)

begin
  do_something()
rescue Errno::ETIMEDOUT => e
  // This will only rescue Errno::ETIMEDOUT exceptions
end

Now that you know how to rescue all errors, you should know that it's usually a bad idea, a code smell, considered harmful, etc.

You can even rescue multiple kinds of exceptions in the same rescue block, so no excuses. :)

begin
  do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
end

So take the time and do it right. Rescue specific exceptions.

Questions?

Thank You

Raising an Exception

By Ahmad Hamza

Raising an Exception

  • 584