What Are Exceptions?
Exceptions are Ruby’s way of dealing with unexpected events.begin
1/0
rescue
puts "Got an exception, but I'm responding intelligently!"
end
# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"
The exception still happens, but it doesn’t cause the program to crash because it was “rescued.” Instead of exiting, Ruby runs the code in the rescue block, which prints out a message. This is nice, but it has one big limitation. It tells us “something went wrong,” without letting us know what went wrong.
Exception Objects
All of the information about what went wrong is going to be contained in an exception object.
begin
1/0
rescue => e
puts "#{e.message}"
end
# 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
begin
# Any exceptions in here...
1/0
rescue ZeroDivisionError => e
puts "Exception Class: #{e.class.name}"
puts"Exception Message:#{e.message}"
put"Exception Backtrace:#{e.backtrace}"
end
# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...
Most exception objects will provide you with at least three pieces of data:
1. The type of exception, given by the class name.
2. An exception message
3. A backtrace
All of the information about what went wrong is going to be contained in an
exception object.
begin
# Any exceptions in here...
1/0
rescue ZeroDivisionError => e
puts "Exception Class: #{e.class.name}"
puts"Exception Message:#{e.message}"
put"Exception Backtrace:#{e.backtrace}"
end
# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...
Most exception objects will provide you with at least three pieces of data:
1.The type of exception, given by the class name.
2.An exception message
3.A backtrace
Raising Your Own Exceptions
So far we’ve only talked about rescuing exceptions. You can also trigger your own exceptions. This process is called “raising” and you do it by calling the raise method.begin
# raises an ArgumentError with the message "you messed up!"
raise ArgumentError.new("You messed up!")
rescue ArgumentError => e
puts e.message
end
This being Ruby, raise can be called in several ways:
# This is my favorite because it's so explicit
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!"
Making Custom Exceptions
Ruby’s built-in exceptions are great, but they don’t cover every possible use case.To make a custom exception, just create a new class that inherits from StandardError.
class PermissionDeniedError < StandardError
end
raise PermissionDeniedError.new()
class PermissionDeniedError < StandardError
attr_reader :action
def initialize(message, action)
# Call the parent's 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 don't
# have permission to delete, you might do something like this:
raise PermissionDeniedError.new("Permission Denied",:delete)
The Class Hierarchy
We just made a custom exception by subclassing StandardError, which itself subclasses Exception.
In fact, if you look at the class hierarchy of any exception in Ruby, you’ll find it eventually leads back to Exception. Here, I’ll prove it to you. These are most of Ruby’s built-in exceptions, displayed hierarchically:
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SecurityError
SignalException
Interrupt
StandardError
ArgumentError
UncaughtThrowError
EncodingError
CompatibilityError
ConverterNotFoundError
InvalidByteSequenceError
UndefinedConversionError
FiberError
IOError
EOFError
IndexError
KeyError
StopIteration
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
SystemCallError
ThreadError
TypeError
ZeroDivisionError
SystemExit
SystemStackError
fatal – impossible to rescue
Rescuing errors of a specific class also rescues errors of its child classes.For example, when you rescue StandardError , you not only rescue exceptions with class StandardError but those of its children as well. If you look at the chart, you’ll see this includes ArgumentError,IOError, and many more.
begin
#do something
rescue Exception => e
end
Rescuing All Exceptions (The Wrong Way)
begin#do something
rescue Exception => e
end
The code above will rescue every exception.Don’t do it! It’ll break your program in weird ways.
That’s 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.
There are also some kinds of exceptions — like syntax errors — that really should cause your program to crash. If you suppress them, you’ll never know when you make typos or other mistakes.
Rescuing All Errors (The Right Way)
Go back and look at the class hierarchy chart and you’ll see that all of the errors you’d want to rescue are children of StandardError.
That means that if you want to rescue “all errors” you should rescue StandardError.
begin
do_something
rescue StandardError => e
# Only your app's exceptions are swallowed. Things like SyntaxError are left alone.
end
In fact, 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
Advanced Rescue & Raise
Full Rescue Syntax
you have a situation that requires a more sophisticated approach to rescue an exception. Below is an example of Ruby’s full rescue syntax.begin
do_someting
rescue TooHotError => too_hot
# This code is run if a TooHotError occurs
rescue TooColdError => too_cold
# This code is run if a TooColdError occurs
else
# This code is run if no exception occurred at all
ensure
# This code is always run, regardless of whether an exception occurred
end
• else - is executed when no exceptions occur at all.
• ensure - is always executed, even if exceptions did occur. This is really
useful for actions like closing files when something goes wrong.
• rescue - you know what rescue, is, but I just wanted to point out that
we’re usingmultiple rescues to provide different behavior for different
exceptions.
begin
request_web_page
rescue TimeoutError
retry_request
rescue
send_alert
else
record_success
ensure
close_network_connection
else
Here, we respond to timeout exceptions by retrying. All other exceptions trig-
ger alerts. If no error occurred, we save the success in the database. Finally,
regardless of what else occurred, we always close the network connection.