Exceptions in Ruby
In Ruby, Exceptions are created using raise command:
1begin 2 raise "boom" 3end
If we execute that code then we will see the error:
1 boom (RuntimeError)
Notice that Ruby is saying that it is a RuntimeError. Here is Ruby's official documentation about RuntimeError. Above code can also be written like this:
1raise RuntimeError, "boom"
As per Ruby's documentation if when we do not mention any class while raising an exception then by default it is RuntimeError class.
In Ruby all exceptions are subclasses of Exception class.
Hierarchy of Ruby Exception class
Ruby has lots of built in exceptions. Here is hierarchy of all Ruby's exceptions:
1Exception 2 NoMemoryError 3 ScriptError 4 LoadError 5 NotImplementedError 6 SyntaxError 7 SecurityError 8 SignalException 9 Interrupt 10 StandardError 11 ArgumentError 12 UncaughtThrowError 13 EncodingError 14 FiberError 15 IOError 16 EOFError 17 IndexError 18 KeyError 19 StopIteration 20 LocalJumpError 21 NameError 22 NoMethodError 23 RangeError 24 FloatDomainError 25 RegexpError 26 RuntimeError 27 SystemCallError 28 Errno::* 29 ThreadError 30 TypeError 31 ZeroDivisionError 32 SystemExit 33 SystemStackError
The rescue method catches a class and all its subclasses
1begin 2 do_something 3rescue NameError 4end
Here we are rescuing all NameError exceptions. However NoMethodError will also be rescued because NoMethodError is a subclass of NameError.
Raising error using class
Following two lines do the same thing:
1raise "boom" 2raise RuntimeError, "boom"
We can raise exceptions of a particular class by stating the name of that exception class:
1raise ArgumentError, "two arguments are needed" 2raise LoadError, "file not found"
Default rescue is StandardError
rescue without any argument is same as rescuing StandardError:
1begin 2rescue 3end
Above statement is same as the one given below:
1begin 2rescue StandardError 3end
Catching multiple types of exceptions in one shot
We can catch multiple types of exceptions in one statement:
1begin 2rescue ArgumentError,NameError 3end
Catching exception in a variable
We can catch exception in a variable like this:
1begin 2rescue StandardError => e 3end
Here e is an exception object. The three main things we like to get from an exception object are "class name", "message" and "backtrace".
Let's print all the three values:
1begin 2 raise "boom" 3rescue StandardError => e 4 puts "Exception class is #{e.class.name}" 5 puts "Exception message is #{e.message}" 6 puts "Exception backtrace is #{ e.backtrace}" 7end
Custom exceptions
Sometimes we need custom exceptions. Creating custom exceptions is easy:
1class NotAuthorizedError < StandardError 2end 3 4raise NotAuthorizedError.new("You are not authorized to edit record")
NotAuthorizedError is a regular Ruby class. We can add more attributes to it if we want:
1class NotAuthorizedError < StandardError 2 attr_reader :account_id 3 4 def initialize(message, account_id) 5 #invoke the constructor of parent to set the message 6 super(message) 7 8 @account_id = account_id 9 end 10end 11 12raise NotAuthorizedError.new("Not authorized", 171)
rescue nil
Sometimes we see code like this:
1do_something rescue nil
The above code is equivalent to the following code:
1begin 2 do_something 3rescue 4 nil 5end
The above code can also be written like so, since by default StandardError is raised:
1begin 2 do_something 3rescue StandardError 4 nil 5end
Exception handling in Ruby on Rails using rescue_from
A typical controller could look like this:
1class ArticlesController < ApplicationController 2 def show 3 @article = Article.find(params[:id]) 4 rescue ActiveRecord::RecordNotFound 5 render_404 6 end 7 8 def edit 9 @article = Article.find(params[:id]) 10 rescue ActiveRecord::RecordNotFound 11 render_404 12 end 13end
We can use rescue_from to catch the exception.
The rescue_from directive is an exception handler that rescues the specified exceptions raised within controller actions and reacts to those exceptions with a defined method.
For example, the following controller rescues ActiveRecord::RecordNotFound exceptions and passes them to the render_404 method:
1class ApplicationController < ActionController::Base 2 rescue_from ActiveRecord::RecordNotFound, with: :render_404 3 4 def render_404 5 end 6end 7 8class ArticlesController < ApplicationController 9 def show 10 @article = Article.find(params[:id]) 11 end 12 13 def edit 14 @article = Article.find(params[:id]) 15 end 16end
The advantage to rescue_from is that it abstracts the exception handling away from individual controller actions, and instead makes exception handling a requirement of the controller.
The rescue_from directive not only makes exception handling within controllers more readable, but also more regimented.
Do not use exception as control flow
Let's look at the following code:
1class QuizController < ApplicationController 2 def load_quiz 3 @quiz = current_user.quizzes.find(params[:id]) 4 rescue ActiveRecord::RecordNotFound 5 format.json { render status: :not_found, json: { error: "Quiz not found"}} 6 end 7end
In the above code when quiz id is not found then an exception is raised and then that exception is immediately caught.
Here the code is using exception as a control flow mechanism. What it means is that the code is aware that such an exception could be raised and is prepared to deal with it.
The another way to deal with such a situation would be to not raise the exception in the first place. Here is an alternative version where code will not be raising any exception:
1class QuizController < ApplicationController 2 def load_quiz 3 @quiz = current_user.quizzes.find_by_id(params[:id]) 4 unless @quiz 5 format.json { render status: :not_found, json: { error: "Quiz not found"}} 6 end 7 end 8end
In the above case instead of using find code is using find_by_id which would not raise an exception in case the quiz id is not found.
In Ruby world we like to say that an exception should be an exceptional case. Exceptional case could be database is down or there is some network error. Exception can happen anytime but in this case code is not using catching an exception as a control flow.
Long time ago in the software engineering world GOTO was used a lot. Later Edsger W. Dijkstra wrote a famous letter Go To Statement Considered Harmful. Today it is a well established that using GOTO is indeed harmful.
Many consider using Exception as a control flow similar to using GOTO since when an exception is raised it breaks all design pattern and exception starts flowing through the stack. The first one to capture the exception gets the control of the software. This is very close to how GOTO works. In Ruby world it is well established practice to not to use Exception as a control flow.
There is nothing to commit in this chapter since all we had done was learning the basics of exception handling in Ruby.