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
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
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
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
catch will then return that variable:
> exposed_secret = catch(:var) do > secret = "bananas!" > throw(:var, secret) > end #=> "bananas!"