Custom Exception Handling in Ruby

Published on 2022-02-13

Developed with an emphasis on programming productivity and simplicity, Ruby is an interpreted, high-level, general-purpose programming language that supports multiple programming paradigms. It supports procedural, object-oriented, and functional programming and uses garbage collection. Ruby is dynamically typed and uses just-in-time compilation.

What is a custom exception in Ruby?

An exception in Ruby is a technique of indicating that something has gone wrong. While other languages only handle exceptions in extremely rare cases, such as runtime problems, Ruby does so in almost every case. Exceptions are aberrant circumstances that appear in the code section as objects during runtime. There are some standard Exception classes, but we can also design our own custom exceptions.

The practice of dealing with aberrant situations, often known as undesirable events, is known as exception handling. In Ruby, there is a particular class called “Exception” that has methods specifically designed to deal with unexpected events. Custom exceptions are subclasses or extensions of exceptions. In general, you should extend StandardError or a descendent wherever possible. The Exception class is used to handle virtual machine or system problems and recover them, avoiding forced interruptions.

How to create a custom exception in Ruby?

Creating a custom exception is straightforward and may be completed in three steps:

1. Create a New Class

Exceptions are classes, just like everything in Ruby! Creating a new kind of exception is as simple as creating a class inheriting from StandardError or one of its children.

class YourError < StandardError
end
raise YourError

By convention, new exception classes end in “Error”. You should also place your own exception classes inside a module. That means you should have error classes like these:

ActiveRecord::RecordNotFound

Net::HTTP::ConnectionError

2. Add your message

Ruby exception objects have a message attribute. The more extended bit of text is displayed next to the exception name. If you raise an exception, you can specify a message, like this:

raise YourError, "Your message"

The constructor of your custom error class can be customized to display a default message.

class YourError < StandardError
    def initialize(msg="Your default message")
        super
    end
end

3. Adding a custom data attribute

Adding custom data to your exception is as easy as adding one to any other class. Let’s add an attribute reader to our class and update the constructor.

class YourError < StandardError
    attr_reader :thing
    def initialize(msg="Your default message", thing="mango")
        @thing = thing
        super(msg)
    end
end

begin
    raise YourError.new("your message", "your thing")
rescue => e
    puts e.thing # "your thing"
end

As you can see, it isn’t challenging to create a custom error or exception with Ruby.

The general syntax of custom exception

The general syntax of exception handling is given below:

begin
raise
    #block where exception raise
rescue
    #block where exception rescue
end

Let’s go through some examples to make it more comprehensible:

EXAMPLE #1

Let’s look at an instance of a code that causes an abnormal condition:

a = 12
b = a/0
puts "Hello World"
puts b

The following error will generate due to the above-mentioned Ruby code:

check.rb:7:in '/': divided by 0 (ZeroDivisionError)

You can see that when an Exception is raised, it interrupts the flow of instructions, and statements written after the Exception statement will not execute. To avoid the rest of the instructions being distorted during execution, we may change the above code in the following fashion.

=begin
    Ruby program to show Exception Handling
=end

begin
    a = 12
raise
    b = a/0
rescue
    puts "Exception rescued"
    puts "Hello World"
    puts b
end

Exception rescued
Hello World

EXAMPLE #2

Assume that a user named ABC with the password ABC exists on the switch, but he or she is not permitted to modify the device’s settings. The target device in this example is a Brocade VDX, however the basics are the same regardless of vendor.

#!/usr/bin/env ruby
require 'net/netconf'
class UnauthorizedError < StandardError
end
login = {target: '10.0.0.1', username: 'ABC', password: 'ABC'}
Netconf::SSH.new(login) do |dev|
    begin
        dev.rpc.edit_config do |x|
            x.interface(xmlns: 'urn:brocade.com:mgmt:brocade-interface') {
                x.tengigabitethernet {
                x.name '200/0/18'
                x.description "to sw37 te 201/0/1"
                }
            }
        end
    rescue Netconf::EditError => e
        if e.rsp.xpath('//error-tag').first.content == 'access-denied'
            raise UnauthorizedError, 'User is not authorized for this RPC.'
        end
        raise
    end
end

If we ran this script, we’d obtain the following results:

$ ruby custom_exception.rb
custom_exception.rb:21:in `rescue in block in <main>': User is not authorized for this RPC. (UnauthorizedError)
from harbl.rb:10:in `block in <main>'
from /home/tyler/.rvm/gems/ruby-2.3.0@netconf/gems/net-netconf-0.4.3/lib/net/netconf/transport.rb:27:in `initialize'
from /home/tyler/.rvm/gems/ruby-2.3.0@netconf/gems/net-netconf-0.4.3/lib/net/netconf/ssh.rb:21:in `initialize'
from custom_exception.rb:9:in `new'
from custom_exception.rb:9:in `<main>'

EXAMPLE #3

The most basic custom exception class that we can create will look like this:

class MyCustomException < StandardError
end

With the following class description, we’ll be able to throw a MyCustomException exception while still having access to all of the built-in StandardError class’s features. But what if you wanted to specify a default error message or add data to your own error? To do this, just change the function Object() { [native code]} to take custom parameters:

class MyCustomException < StandardError
    def initialize(msg="This is a custom exception", exception_type="custom")
        @exception_type = exception_type
        super(msg)
    end
end

This class definition allows us to add the following data to our custom exception:

begin
    raise MyCustomException.new "Message, message, message", "Yup"
rescue MyCustomException => e
    puts e.message *# Message, message, message*
    puts e.exception_type *# Yup*
end

EXAMPLE #4

Let’s look at this example:

begin
    #Your code
    cause_an_error()
rescue
    puts "An error has occurred"
end

This is one method for creating a simple ruby exception handler. Rescue without any parameters will catch any StandardError and its subclasses, which is a good method to capture common mistakes.

To obtain access to the exception’s properties, use rescue => var name to assign the exception to a local variable. Consider the following scenario:

# ...rescue => error  puts "#{error.class}: #{error.message}"end

We previously said that rescue captures StandardError and its subclasses on its own. You may also select the type of issue you want to target. You can utilize numerous rescue clauses as a result of this. Consider the following scenario:

begin
    # do something that raises an exception
    do_something()
rescue NameError
    puts "A NameError occurred. Did you forget to define a variable?"
rescue CustomError => error
    puts "A #{error.class} occurred: #{error.message}"
end

EXAMPLE #5

=begin
    Ruby program to show Exception Handling.
=end

def exception_arised
    begin
        puts 'Hello! Welcome to the abc.com.'
        puts 'We are still safe from Exception'
        # using raise to generate an exception
        raise 'Alas! Exception Generated!'
        puts 'After Exception created'
    # using Rescue method to handle exception
    rescue
        puts 'Hurrah! Exception handled! We are safe now'
    end
    puts 'We are out of begin!'
end

#invoking method
exception_arised

The following output will get generated:

Hello! Welcome to the abc.com.
We are still safe from ExceptionHurrah!
Exception handled! We are safe now
We are out of begin!

In the above-described code, we are just creating our exception with the help of a user-defined method, exception_arised. Afterwards, we are simply invoking it.

EXAMPLE #6

While Sinatra (open-source web application library, particularly for the language of Ruby) includes built-in support for graceful error handling, however, creating custom logic to deal with raised problems is occasionally necessary. An error block can be used in the same way as a rescue block to accomplish this. In Sinatra, for example, the following directive might work to fix a User::NotAuthorized error:

error User::NotAuthorized do
# ...
end

Similarly, we might use the following error handler if we want to recover all exceptions produced in a Sinatra application:

error do
# ...
end

EXAMPLE #7

The exception family is often used to handle virtual machine or system problems and recovering them can prevent a forced interruption from occurring.

# Defines a new custom exception called FileNotFound
class FileNotFound < StandardError
end

def read_file(path)
    File.exist?(path) || raise(FileNotFound, "File #{path} not found")
    File.read(path)
end

read_file("missing.txt")  #=> raises FileNotFound.new("File `missing.txt` not found")
read_file("valid.txt")    #=> reads and returns the content of the file

Adding the Error suffix to the end of an exception’s name is common:

ConnectionError

DontPanicError

When the issue is self-explanatory, however, the Error suffix is not required because it would be redundant:

FileNotFoundError vs. FileNotFound

DatabaseExploded vs. DatabaseExplodedError

Conclusion

This ruby technique to managing exceptions goes a long way towards presenting significant errors to your code’s users. Not just can you throw and raise the exceptions, but your users may now catch exceptions from your library in their ruby code. To get the most out of exceptions in your application, it’s not enough to know how to rescue pre-defined exceptions; you also need to know how to create and raise your own. Every Ruby exception is inherited from the Exception class, which comes with a variety of built-in methods, the most popular of which is the message. This method may be used to retrieve a specific exception message from a raised exception object. However, notice how in the examples, we always inherited from StandardError? That was done on purpose. While Ruby has an Exception class, you should never directly inherit from it.