Ruby Exceptions
- 07 March 2017
- Ruby
- Ruby Exceptions
Let's take a break from long reads and discuss something that Ruby-developers use every day. We're going to discuss exceptions. At first sight exceptions in Ruby look as relatively simple concept. But there are couple caveats.
If you don't know how exceptions work in Ruby check this example:
puts a # => undefined local variable or method `a' for main:Object (NameError)
a is an undefined variable and that's why we get this exception with descriptive message: "undefined local variable or method a". Also we see a type of exception: NameError.
If we know that our code can throw an exception, we can catch it using begin...rescue.
begin
puts a
rescue
puts 'Something bad happened'
end
Also, we can define in rescue which exact type of error we want to catch:
begin
puts a
rescue NameError => e
puts e.message
end
To catch different types of exceptions by rescue we can use something like that:
def foo
begin
# logic
rescue NoMemoryError, StandardError => e
# process the error
end
end
It's really important to know that by default rescue catches all errors which are inherited from StandardError. So it's not going to catch NotImplementedError or NoMemoryError for example. To understand which exceptions we can catch by rescue let's check hierarchy of exceptions in Ruby:

As you've noticed in rescue we can define variable by which we can get an access to exception object:
def foo
begin
raise 'here'
rescue => e
e.backtrace # ["test.rb:3:in `foo'", "test.rb:10:in `<main>'"]
e.message # 'here'
end
end
You can get more info about methods of this object here.
Ruby provides another interesting keyword to work with exceptions - ensure. Either exception appears or not - Ruby will execute code inside ensure anyway. Very often developers use this to close connection to db, remove temporary files, etc.
begin
puts a
rescue NameError => e
puts e.message
ensure
# clean up the system, close db connection, remove tmp file, etc
end
There is one important thing to know about ensure. If you explicitly return from ensure but don't not define rescue - ensure will intercept an exception.
Let's check it by example. We will start from implicit return:
def foo
begin
raise 'here'
ensure
puts 'processed'
end
end
foo
# processed
# => `foo': here (RuntimeError)
In this example Ruby executes code inside ensure first and then throws an exception because we didn't catch it by rescue.
Let's consider example with explicit return:
def foo
begin
raise 'here'
ensure
return 'processed'
end
end
puts foo # => processed
No RuntimeError this time! We still don't catch an exception, but Ruby returns 'processed' from ensure and don't throw that error.
Let's learn how to throw exceptions from code. Module Kernel has method raise which allows to throw errors. There is an alias for raise - fail, but you will see raise much more often than fail.
If you call raise without params it will throw this error:
raise # => `<main>': unhandled exception
This exception doesn't say anything to developer, so usually you want to pass at least error message:
raise 'Could not read from database' # => Could not read from database (RuntimeError)
Now we see that raise returned error message and it's a RuntimeError. By default raise throws RuntimeError.
We can define which exact exception we want to throw:
raise NotImplementedError, 'Method not implemented yet'
# => Method not implemented yet (NotImplementedError)
It's interesting that raise calls method #exception for any class you pass to it. In this case it called NotImplementedError#exception. It allows us to add exception support to any class. The main requirement is #exception method:
class Response
# ...
def exception(message = 'HTTP Error')
RuntimeError.new(message)
end
end
response = Response.new
raise response # => HTTP Error (RuntimeError)
There is one more interesting thing about exceptions. When exception appears Ruby stores it in global variable $!.
$! # => nil
begin
raise 'Exception'
rescue
$! # <RuntimeError: Exception>
end
$! # => nil
As we see here we have exception assigned to $! inside rescue part, but right after execution it equals to nil.
Ruby provides us a way to run code inside begin part one more time.
Let's imagine that we have service which doesn't return required data sometimes.
We could wrap requests to that service into loop, but we can use retry, which will execute code inside begin one more time.
tries = 0
begin
tries += 1
puts "Trying #{tries}..."
raise 'Did not work'
rescue
retry if tries < 3
puts 'I give up'
end
# Trying 1...
# Trying 2...
# Trying 3...
# I give up
This code is really simple. If code inside begin throws an exception we try to execute it one more time. This idea is really interesting. But I should notice that for better error handling of third-party services there is a better solution called Circuit Braker: article, gem.
Also I would like to mention that if there was an exception and program finishes execution - Ruby will execute code inside callback at_exit:
at_exit { puts 'going to exit' }
raise 'exception'
# => going to exit
# => exception (RuntimeError)
Ruby will execute it even if program is exiting with an exception.
The last interesting approach that Ruby-developers use - they use rescue on method level without explicit definition of begin.
def set_post
@post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to posts_path, alert: "Post Not Found"
end
It's an example of Rails-application which wants to find Post by id. If it doesn't find it will throw an exception ActiveRecord::RecordNotFound which we can catch by rescue and redirect user to posts_path.
Ruby it's a really laconic language, so it allows us to do shorter version:
def foo
# ...
rescue
# ...
end
It's much better than this:
def foo
begin
# ...
rescue
# ...
end
end
Long story short
Things to remember:
raiseby default throwsRuntimeErrorrescueby default catches onlyStandardErrorand all inherited exceptions- explicit
returnfromensurewithout error handling will intercept that exception - during error handling Ruby stores exception in global variable
$! - Ruby has
retry at_exitwill be executed even if program exits because of excecption- use
rescueby method-level to make code shorter
I hope you've found some interesting ideas in this article. Send me a message if you also know something interesting about Exceptions in Ruby.
I've wrote this post after watching video by Avdi Grimm. You'll find couple more ideas there.
Thanks for reading!
