Ahmad Hamza
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 0Just 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 ?
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 => eZeroDivisionError 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...'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):6raise
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# 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!"Steps:
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 ArrayYou 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.
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
endJust 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:
Exception and its children
// Don't do this
begin
do_something()
rescue Exception => e
...
endDon'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.
begin
do_something()
rescue StandardError => e
# Only your app exceptions are swallowed. Things like SyntaxErrror are left alone.
endif 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
endbegin
do_something()
rescue Errno::ETIMEDOUT => e
// This will only rescue Errno::ETIMEDOUT exceptions
endNow 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
endSo take the time and do it right. Rescue specific exceptions.