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.