Ruby Exceptions

Education is not limited to just classrooms. It can be gained anytime, anywhere... - Ravi Ranjan (M.Tech-NIT)

Ruby Exceptions

Ruby exception is an object, an instance of the class Exception or descendent of that class. It represents some exceptional condition.

In a Ruby program, when something goes wrong, it throws an exceptional behavior. By default Ruby program terminates on throwing an exception.

We can declare some exception handlers within Ruby. An exception handler is a block of code which is executed when exception occurs in some other block of code.

Exceptions are handled in two ways. Either you can terminate the program or deal with the exception. To deal with an exception, you can provide a rescue clause. By providing this, program control flows to the rescue clause.

When an exception is raised but not handled, global variable $! contains the current exception and $@ contains the current exception's backtrace.

Ruby predefined classes like Exception and its children helps you to handle errors of your program. In Ruby exception hierarchy, most of the sub classes extend class StandardError. These are the normal exceptions.


Ruby Class Exceptions

Built-in subclasses of exception are as follows:

  • NoMemoryError
  • ScriptError
  • SecurityError
  • SignalException
  • StandardError
  • SystenExit
  • SystemStackError
  • fatal - impossible to rescue

Example:

  1. def raise_exception     
  2.   puts 'I am before the raise.'     
  3.   raise 'oops! An error has occured'     
  4.   puts 'I am after the raise'     
  5. end     
  6. raise_exception

Output:

Ruby exceptions 1

The raise method comes from the Kernel module

Handling an Exception

To handle exception, the code that raises exception is enclosed within begin-end block. Using rescue clauses we can state type of exceptions we want to handle.

Example:

  1. def raise_and_rescue     
  2.   begin     
  3.     puts 'Before the raise.'     
  4.     raise 'An error occured.'     
  5.     puts 'After the raise.'     
  6.   rescue     
  7.     puts 'Code rescued.'     
  8.   end     
  9.   puts 'After the begin block.'     
  10. end     
  11. raise_and_rescue

Output:

Ruby exceptions 2

If no argument is defined in the rescue clause, the parameter defaults to StandardError. Each rescue clause specify multiple exceptions to catch. If raise is used without any parameters, exception may re-raised.

The rescue clauses are written in a begin/rescue block. Exceptions if not handled by one rescue clause will br handled with the next one.

In the begin block, each rescue clause with the raised exception will be compared against each of parameters in turn. It will be matched when the type of error thrown and exception named in the rescue clause is either same or is a superclass of that exception. The else clause is executed if body of begin statement is completed without exceptions. If an exception occurs, else clause will not be executed.


Exception Object

Exception objects are normal objects. A rescued exception can be hold to a variable within the rescue clause.

Example:

  1. begin   
  2.   raise 'an exception'   
  3. rescue ZeroDivisionError => e   
  4.   puts "Exception Class: #{ e.class.name }"   
  5.   puts "Exception Message: #{ e.message }"   
  6.   puts "Exception Backtrace: #{ e.backtrace }"   
  7. end

The Exception class defines two methods that return details about the exception. The message method returns a string that define the explanation of error. The backtrace method returns an array of string that represent the call stack at that point where exception was raised.


Using retry Statement

Usaually in a rescue clause, the exception is captured and code resumes after begin block. Using retry statement, the rescue block code can be resumed from begin after capturing an exception.

Syntax:

  1. begin  
  2.    code....  
  3. rescue  
  4.     # capture exceptions  
  5.     retry  # program will run from the begin block  
  6. end  

Example:

  1. #!/usr/bin/ruby   
  2.   
  3. begin   
  4.    x = Dir.mkdir "alreadyExist"   
  5.    if x   
  6.       puts "Directory created"   
  7.    end   
  8. rescue   
  9.    y = "newDir"   
  10.    retry   
  11. end   

The above program runs as follows:

Step 1 In the begin block, code is written to make a directory that already exists.

Step 2 This will throw an error.

Step 3 In rescue block, y was reassigned.

Step 4 The retry statement will go to the begin block.

Step 5 Directory will be created.


Using raise Statement

The raise statement is used to raise an exception.

Syntax:

raise

Or,

raise "Error Message" 

Or,

raise ExceptionType, "Error Message"

Or,

raise ExceptionType, "Error Message" condition

The first one re-raises the current exception. It is used for exception handlers where exception is intercepted before passing it on.

The second one creates a new RuntimeError exception. This exception is then raised up the call stack.

The third one uses first argument to create an exception, then sets associated message to the second argument.

The fourth one similar to third one. In this you can add any conditional statement to raise an exception.

Example:

  1. #!/usr/bin/ruby   
  2.   
  3. begin     
  4.     puts 'code before raise.'     
  5.     raise 'exception occurred.'     
  6.     puts 'code after raise.'     
  7. rescue     
  8.     puts 'I am rescued.'     
  9. end     
  10. puts 'code after begin block.'

Output:

Ruby exceptions 3


Using ensure Statement

There is an ensure clause which guarantees some processing at the end of code. The ensure block always run whether an exception is raised or not. It is placed after last rescue clause and will always executed as the block terminates.

The ensure block will run at any case whether an exception arises, exception is rescued or code is terminated by uncaught exception.

Syntax:

  1. begin   
  2.   code..  
  3.    #..raise exception  
  4. rescue   
  5.    #.. exception is rescued  
  6. ensure   
  7.    #.. This code will always execute.  
  8. end 

Example:

  1. begin   
  2.   raise 'Exception'   
  3. rescue Exception => e   
  4.   puts e.message   
  5.   puts e.backtrace.inspect   
  6. ensure   
  7.   puts "The ensure code will always run"   
  8. end  

Output:

Ruby exceptions 4

Using else Statement

The else clause is always present after rescue clause and before ensure clause. If no exceptions are raised, then only else block is executed.

Syntax:

  1. begin   
  2.    code..   
  3.    #..raise exception  
  4. rescue   
  5.    # .. exception is rescued  
  6. else  
  7.    #.. executes if there is no exception  
  8. ensure   
  9.    #..  This code will always execute.  
  10. end  

Example:

  1. begin   
  2.  # raise 'A test exception.'   
  3.  puts "no exception is raised"   
  4. rescue Exception => e   
  5.   puts e.message   
  6.   puts e.backtrace.inspect   
  7. else   
  8.    puts "else code will be executed as no exception is raised."   
  9. ensure   
  10.   puts "ensure code will run"   
  11. end  

Output:

Ruby exceptions 5

 

Ruby Catch and Throw

Ruby catch and throw provide a way to jump from the execution early when no further work is needed in a code.

The catch defines a block that is labeled with a given name. It is used to jump out of nested code. Using catch, the block will be executed normally until throw is encountered.

The catch and throw method is faster than rescue and raise clauses. Hence, it is more suitable to use.

Syntax:

  1. throw :lablename  
  2. #.. this  code will not be executed  
  3. catch :lablename do  
  4. #.. matching catch will be executed after a throw is encountered.  
  5. end

Or,

  1. throw :lablename condition  
  2. #.. this code will not be executed  
  3. catch :lablename do  
  4. #.. matching catch will be executed after a throw is encountered.  
  5. end  

Example:

  1. def promptAndGet(prompt)   
  2.    print prompt   
  3.    res = readline.chomp   
  4.    throw :quitRequested if res == "!"   
  5.    return res   
  6. end   
  7.   
  8. catch :quitRequested do   
  9.    name = promptAndGet("Name: ")   
  10.    age = promptAndGet("Occupation: ")   
  11.    # ..   
  12.    # process information   
  13. end   
  14. promptAndGet("Name:"

Output:

Ruby exceptions 6