These free mini-courses will give you a strong foundation in web development. Track your progress and access advanced courses on HTML/CSS, Ruby and JavaScript for free inside our student portal.
Scroll down...
In the real world, errors happen. You've encountered them hundreds of times already and will certainly encounter them thousands of times in the future. Building your application in a way that gracefully responds to unexpected situations with appropriate error messages is a necessary art.
In this lesson, we'll introduce you to the basics of how Ruby handles errors and how you can implement them in your own code.
"Errors" and "Exceptions" are effectively the same thing in Ruby. Technically speaking, all of Ruby's errors are actually subclasses of the Exception class.
You hit an exception when the program encounters something which it cannot handle. Ruby then stops the program and starts backing up through the program's stack (the nested chain of method calls that got you to where you are), looking for something along the way that can handle what happened. If the error cannot be handled, it is displayed to you either in the browser or on the command line.
Ruby provides a straightforward way of handling errors. You simply wrap the piece of code that might generate an error in a begin
block and use rescue
to specify which error(s) you are willing to handle from it. ensure
will always run and retry
allows you to try again from the beginning:
begin
File.open("nonexistant.txt","r")
rescue SomeSpecificException
# handle that exception
retry # to try the original code again
rescue SomeOtherException
# handle that exception
else
# handle any remaining exceptions
ensure
# this code is always run
puts "we found an error, and are now sad."
end
rescue
will match the specific error or any of its superclasses and run the provided code if a match is found.
Code within the ensure
is always run. This is optional.
retry
goes back to the begin
and tries again. This is optional. Careful -- this could cause an infinite loop if you're not careful!
You can also raise your own errors using the raise
method. The simplest way is to just provide a text string naming the error yourself:
> begin
> raise "random exception found"
> rescue
> puts "caught the exception"
> end
caught the exception
#=> nil
raise
lets you choose how specific you'd like to be:
raise # raises default RuntimeException
raise "Error message"
raise ExceptionType, "Error Message"
rescue
allows you to have access to the Exception object if you pass it in and you can use that to be more descriptive in your response, for instance by displaying a more specific error message or viewing the backtrace:
begin
3/0
rescue Exception => e
puts e.message
puts e.backtrace.inspect
end
#divided by 0
#["(irb):5:in `/'", "(irb):5:in `irb_binding'" ... ]
When raising your own errors, you can raise an existing exception (like StandardError) or create your own.
Exceptions are all descendants of Ruby's Exception class. The first layer of children for Exception provide you with some ample fodder for raising errors:
You can see the full hierarchy here if you want to raise a specific exception
begin
raise StandardError if some_condiditon
rescue StandardError
puts "found a standard error around here!"
end
If you want to create your own, it needs to be a subclass of either Exception or one of its descendants. In practice, you should always inherit from StandardError or one of its descendants:
class BogusError < StandardError
end
# IRB
> raise BogusError.new
# BogusError: BogusError
# from (irb):3
When you're building toy apps, you can rely on Ruby's pre-existing error handling to cover most cases. When you start building larger applications and encountering situations that should be fatal but aren't, you'll want to set up your own error handling.
Some good candidates for error handling include any interfaces with either the user or with other applications (whether locally or via web APIs). Interfaces are where the unexpected should be expected...
As a beginner, it can be tempting to simply ignore errors by wrapping troublesome bits of code in begin
/ rescue
blocks but that's really not fixing the problem and is a bad habit to get into.
Perhaps more alarmingly, beginners often think of exceptions as great ways to control the flow of code. If you're nested 5 loops deep, it can be tempting to just throw an error and catch it at the top. BAD! Exceptions are ONLY for EXCEPTIONAL circumstances which should cause program failure.
There's undeniable power in the idea of using exceptions for flow control, though... so good thing Ruby provides the throw
and catch
methods which behave the same way. You don't often see this pattern in the wild because it can make the code more difficult to follow but it's good to know nonetheless.
throw
acts like a return
statement except it only jumps you as far as the catch
which wraps it. It can be helpful for breaking out of nested loops when otherwise a break
would be insufficient.
Simply wrap your code in a catch
block and then throw
yourself out of it when you're ready. Make sure they take matching symbols so Ruby knows which catch
to send your throw
to:
def floor_sweeping
floor = [["blank", "blank", "blank"],
["sand", "blank", "blank"],
["blank", "blank", "blank"]]
dirt_type = nil
catch(:swept) do # <<<<<<<
floor.each do |row|
row.each do |tile|
if tile == "sand"
dirt_type = tile
throw(:swept) # <<<<<<<
end
end
end
end
puts dirt_type
end
# IRB
> floor_sweeping
#=> sand
You can also pass variables into throw
and catch
will then return that variable:
> exposed_secret = catch(:var) do
> secret = "bananas!"
> throw(:var, secret)
> end
#=> "bananas!"