Rescue Exceptions in Ruby
In the Ruby programming language, exceptions are a common feature. About 30 different exception subclasses are defined in the Ruby standard library, some of which have their subclasses. Ruby's exception mechanism is quite strong, yet it is frequently misused.
This article will go over how to use rescue exceptions in ruby and how to handle them.
Here’s how it is done:
- What is an Exception?
- What is Exception Handling?
- When to Handle an Exception?
- When do Exceptions Occur?
- Exception handling in Ruby
- Be Specific About Your Rescue
- The begin-rescue
What is an Exception?
A program error condition is represented by an exception. Exceptions are a mechanism for suspending a program's execution. They work in the same way as "break" in that they cause the instruction pointer to jump to a different location. The location, unlike break, could be another tier of the program stack. Ruby programs crash when unhandled exceptions occur.
What is Exception Handling?
Software systems are typically prone to errors. Exceptions are more likely to occur in systems that include human involvement because they attract errors on numerous levels.
Syntactical errors, network errors, form input errors, invalid authentication errors, and so on are all examples of errors. These can damage user experience and potentially emerge as security loopholes, allowing attackers to compromise the system if not addressed.
Exception handling prevents system failures and saves the user from annoying error messages.
When an error condition occurs, the programming language usually compels the program to terminate, and control of the program is lost. Exception handling allows you to maintain control and respond to what occurs after an error has occurred.
This offers you some flexibility to make changes, such as displaying friendly error messages, releasing resources, or quickly redirecting the user to a relevant website.
Exception handling assists you in cleaning up the mess without making it appear such. It is the process of tolerating error conditions and responding to them programmatically, resulting in the execution of a supposedly alternate but already planned code sequence.
Read about Exception Handling in PHP 8.
When to Handle an Exception?
Handling exceptions follows a simple rule: handle only those exceptions over which you have control. It's simple to say, but it's not always easy to do. We tend to want to save every single possible exception.
We commonly log a message and resume execution since we don't know what to do with an exception. This frequently leads to the creation of unnecessary code to deal with the failures.
We should only handle exceptions when we have a reasonable chance of correcting the issue and allowing our application to continue running.
When creating code, we must consider three primary kinds of exceptional behavior: possible, probable, and inevitable.
- Possible Exceptions
Theoretically, exceptions are possible, but they are unlikely to occur in the system. When such exceptions occur, it usually means that the system is seriously broken. The issue is irreversible in this scenario, and we should not attempt to handle the exceptions. - Probable Exceptions
Probable exceptions could occur during the execution of the program, such as a REST call failure caused by a DNS issue. These are the issues we can foresee while developing our program, and in some situations, we can identify a solution. This is where we should concentrate the majority of our exception handling efforts. - Inevitable Exceptions
Inevitable exceptions will occur frequently. Allowing these exceptions is a typical practice. The proactive detection of the exceptional state and branching around it is a more successful method. Exceptions should not be utilized to control the flow of the program. If we can predict that an operation would result in an exceptional state, we should avoid it wherever possible.
When do Exceptions Occur?
When an exception occurs, take corrective action. Exception handling is about making our programs behave more civilized. Beginning, rescuing, and burying exceptions are anti-patterns that should be avoided.
However, there are situations when the only thing we can do is report an exception. The most basic form of this reporting is to print information to standard error, indicating that an exception has occurred and maybe providing instructions on how to resolve the problem.
More advanced systems use logging and APM technologies like Atatus to report concerns. APM tools streamline the process by centralizing issue reporting and monitoring and promptly identifying ways to improve performance and resolve hidden exceptions.
Atatus support Ruby applications to ensure that no errors are missed while deployments are pushed into production.
Exception handling in Ruby
Exception handling is performed in the majority of languages utilizing "try, throw, and catch" techniques. When the try block is reached, the compiler becomes aware of the possibility of an exception (thrown either by the user or by the language itself) and informs the catch block to handle it.
Read about PHP Exception Handling Using Try Catch.
Ruby chose alternative names for their standard exception handling system. There is a beginning, which is followed by errors that are raised and subsequently rescued. It comes to an end, like all wonderful things, unless you choose to retry.
begin
# something which might raise an exception
rescue
# code that deals with some exception
retry
# retry the code in the begin block
ensure
# ensure that this code always runs, no matter what
end
In Ruby, exception handling comprises-
- Begin - End block
- Raise
- Rescue
- Retry
- Ensure
The programming concepts of try, throw, catch, and finally are analogous to the begin-end block, raise, rescue, and ensure.
Be Specific About Your Rescue
What your rescue statement can handle should be specified. Use the most general class name possible if your rescue block can handle many erroneous conditions. This can be StandardError in some circumstances, although it's most typically a subtree of the class hierarchy behind StandardError.
StandardError and all of its subtypes are captured by a basic rescue, which means it will catch any class raised that extends StandardError. This is troublesome. Only those exceptions should be rescued over which you have control. Other exceptions should be permitted to pass through your rescue statement.
If you use an APM tool like Atatus, you can collect every error that you can't fix and are just causing unnecessary noise.
Every Ruby error is monitored using error tracking and captured with a full stack trace with the specific line of source code underlined to make error fixing easier and faster. To solve Ruby exceptions and errors, you can collect all the necessary information such as class, message, URL, request agent, version, and so on. You can also discover buggy APIs or third-party services by investigating API failure rates and application crashes.
The begin-rescue
Ruby's exception handling starts with the begin-rescue block, similar to PHP's try-catch. In short, the begin-rescue code block is used to handle raised exceptions without interrupting program execution. In other words, you can start executing a block of code while rescuing any raised exceptions.
#1 Rescuing Exceptions
Begin-rescue saves all instances of the StandardError class by default. This includes no method errors, type errors, runtime errors, as well as a custom error that is designed to be rescued within a Ruby application.
Simply enclose the designated section of code in a begin-rescue block to rescue every StandardError:
begin
# Raise an error here
rescue => e
# Handle the error here
end
When a StandardError exception is thrown in the begin block, an instance of it is provided to the rescue block as variable e.
#2 Rescuing Specific Exceptions
While rescuing every exception raised is great for simple implementations like generalizing API error responses, the best practice is to rescue for specific exceptions.
To accomplish this, update the above general begin-rescue block to specifically rescue StandardError exceptions:
begin
# Raise an error here
rescue StandardError => e
# Handle the error here
end
Although the difference may be minor, if you use a class name after the rescue command, only exceptions of that type will be rescued.
If we wanted to rescue all argument errors, for example, we could build our begin-rescue block as follows:
begin
# Raise an error here
rescue ArgumentError => e
# Handle the error here
end
But what if we need to save multiple exception types?
A begin-rescue block can have many rescues, similar to an if-elsif-else chain, which, when combined with a check for the StandardError class, allows you to logically adapt to any issues that may arise:
begin
# Raise an error here
rescue ArgumentError => e
# Handle the argument error
rescue TypeError => e
# Handle the type error
rescue => e
# Handle the other errors
end
#3 Rescuing All Exceptions
While it may be tempting to rescue every child of the Exception class, due to the structure of the Ruby exception hierarchy, this is typically considered bad practice. This is because, while all Ruby exceptions and errors are extensions of the Exception class, many of them are reserved for Ruby's internal usage.
SignalException::Interrupt, for example, is used to signal that Ctrl-C has been detected during script execution.
The Interrupt exception would not work as expected if you rescued every child of the Exception class. However, if you wish to rescue every exception raised in Ruby, you may use the same begin-rescue block to specify rescue all Exception exceptions:
begin
#...
rescue Exception => e
#...
end
Use the above begin-rescue block sparingly and with caution because it will rescue any exception and error that is raised, from interruptions to syntax errors, and even memory errors.
#4 Rescuing Loops
The "rescue" keyword can also be used on loops. After all, in Ruby, loops are just statements.
The rescue syntax for the various loops is as follows:
while do
#... loop body
end rescue ...error handler...
begin
#... loop body
end while rescue ...error handler...
until do
#... loop body
end rescue ...error handler...
for in expression do
#... loop body
end rescue ...error handler...
When using a rescue on a loop, there are a few things to keep in mind. The rescue is carried out after the loop has been completed.
#5 Rescue Each
You might be asking whether "rescue" can be used with an "each" iterator. Yes, it is correct.
The following is the syntax:
.each {} rescue ...error handler...
Conclusion
We learned that Ruby has different terminology for handling exceptions. Except for the retry function, the begin/raise/rescue paradigm is similar to the try/throw/catch paradigm used in most other programming languages.
In Ruby, however, throwing and catching allow you to get out of complex constructions quickly. We also learned how to extend the StandardError class to construct application-specific custom Exception classes.
Ruby's rescue block is quite powerful. It's far more user-friendly than error codes. Rescue enables you to build more robust solutions by giving you a simple approach to dealing with typical software problems.
Monitor Your Ruby Applications with Atatus
Atatus keeps track of your Ruby application to give you a complete picture of your clients' end-user experience. You can determine the source of delayed response times, database queries, and other issues by identifying backend performance bottlenecks for each API request.
To make bug fixing easier, every Ruby error is captured with a full stack trace and the specific line of source code marked. To assist you in resolving the Ruby error, look at the user activities, console logs, and all Ruby requests that occurred at the moment. Error and exception alerts can be sent by email, Slack, PagerDuty, or webhooks.