Error Handling in Rails
One aspect of development that often gets overlooked or underestimated when building applications is arrow handling, but it is the most important part of development since without it your application can stop without any notice, leaving behind a generic error that doesn’t give information about the context of the error. In this article, I will show you some aspects of handling errors in Rails and some common mistakes that I have seen in the wild and that you should avoid.
Let’s start with the definition of an error. Errors are situations or anomalies that occur during the execution of a program, that can cause said program to stop abruptly depending on the situation. In Ruby particularly, errors are treated as objects, so when an error (also known as an exception) occurs, this object gets created and raised so they can be caught and handled. To take care of an error (handle the error), a block of code has to be provided that will execute when the error occurs and in it, define what we will do to resolve the error or log it for more information. As I stated before, your application can go down without realizing it if you don’t handle errors.
Just as with any other object, there are different types of errors in Ruby, the type is determined by the class of error that is thrown, the one that most of the other error types inherit is called StandardError but there are others like TypeError, ZeroDivisionError, ArgumentError, and more. Rails has additional errors on top of the ones that Ruby has, for example, ActiveRecord validation errors, and controller-level errors.
Now that we know what an error is, let’s learn how to handle them. Ruby has different clauses to define what is going to be tested and how the error that it might raise from that test block will be handled. The first clauses that Ruby provides are the Begin and Rescue blocks. Inside the Begin block you write the code that you expect will throw an error at some point, and in the Rescue block, you put the code that will handle the error itself.
For the Rescue block, you can define what type of error it handles and you can provide one Rescue block for each type that you are expecting, so each error gets a different code to handle it.
You can also use the Raise method to throw any error that you consider applies to the flow of your code. There is another clause called Ensure that can be used in combination with the Begin and Rescue blocks, any code that goes inside it is warranted to run no matter if there is an error or not, this block is not commonly used but it is an option in case you need it.
Rails handles errors following a hierarchy, if an error occurs the first Rescue block that it encounters going up the call stack is the one that is going to be executed. If there is no Rescue block then the default error handler is the one that gets called, but it is just a reporting block, so if the conditions that generated the error are not fixed and are serious enough, most likely, the application will stop.
Now there is a concept when working with errors that is referred to as bubbling up, this is when you have a block that handles the error itself but you want to keep it going up the call stack so other errors also activate, so you re-raise them, this allows you to segment your error handling blocks or concentrate all the error handling in certain areas of the code. It is worth noting that if you don’t bubble up the error, it ends within that rescue block. Bubbling up can be useful when you want to have the first block log the error to an external service for detailed information but also want to log it locally with the default Rails error handler.
There are some common mistakes that we can fall into when handling errors in Rails applications that you have to avoid to prevent erratic behavior. One mistake I see a lot is rescuing the Exception object, this catches any object from the exception class (this includes errors), and doing so can have some bad consequences like not being able to kill your application with any of the known methods, lock up the process and others. The recommended way to handle errors is to specify the type that you want to target and if you don’t know it then use StandardError as a generic type.
Another mistake that I see is that if you find yourself repeating the same error-handling logic throughout your application, extract it into an error-handling module and use it as a mixin in any of the classes you create or include it in a higher-level class so all its children can have access to it.
Thank you for reading this article and I hope it helped you in your journey of developing applications with Rails. If you like it give it a clap and check out my other articles on different development topics and consider following me for more articles like this.