Error Handling in Rails — The Modular Way

A Modular approach to handle errors in Rails.

Sudharsanan Muralidharan
Rails, Ember & Beyond
5 min readJun 24, 2016

--

Image Credits: unsplash.com

Murphy’s Law:

As Murphy’s Law states anything that can go wrong will go wrong, which is why it is important to be prepared for it. It applies everywhere, even in Software Development. The application that we develop must be robust enough to handle it. In other words it must be Resilient. This is exactly what this blog post is about.

Anything that can go wrong, will go wrong.

— Murphy’s Law

In Rails a typical workflow we handle errors in the Controller level. Let say you are writing an API using Rails. Consider the following controller method to render user JSON.

users_controller.rb

When the user object is found it renders it as json, otherwise renders the error json. This is the typical way of writing a show method is Rails. But here is the catch. When user record is not found it doesn’t go into the else block it renders the fallback 500.html content. Well that was unexpected. This is because if record is not found it raises a RecordNotFound error. The same happens with find_by! or any finder methods with a bang.

ActiveRecord::RecordNotFound

Exception != Error

Before we get to rescuing errors there something important we need to understand. As seen in the above example we get an ActiveRecord::RecordNotFound error. The try catch block in ruby would look something like this which works just fine.

rescue from RecordNotFound error

But when you want to rescue from all Exceptions then it is really important to know the difference between Exception and Error in Ruby. Never rescue from Exception.It tries to handle every single exception that inherits from the Exception class and ultimately stops the execution.

rescue from Exception

Instead we need to rescue from StandardError. Here is an excellent blog post which explains the difference http://blog.honeybadger.io/ruby-exception-vs-standarderror-whats-the-difference/.

rescue from StandardError

The Rescue

In order to handle the errors we can use the rescue block. The rescue block is similar to the try..catch block if you are from the Java world. Here is the same example with a rescue block.

begin..rescue

With this approach the errors are rescued in the controller methods. Though this works perfectly it might not be best approach to handle errors. Here are a few reasons to consider an alternate approach.

  1. Fat Controllers: Do read this excellent article by Thoughtbots https://robots.thoughtbot.com/skinny-controllers-skinny-models.
  2. The DRY principle: We are simply repeating the error block in various places that goes against the DRY(Don’t Repeat Yourself) principle.
  3. Maintainability: Harder to maintain code. Changes to the error such as the format would involve major changes.

An alternate approach would be to move the error handling block to the ApplicationController. A much cleaner approach is to write an Error handler module.

Error Handling — Modular Approach

In order to handle the errors in one place our first option would be to write in under ApplicationController. But it’s best practice to have it separated from the Application logic.

Let us create a module which handles errors on a global level. Create a module ErrorHandler(error_handler.rb) and place it under lib/error (or anywhere to load from) and then include it in our ApplicationController.

Important: Load the Error module on App startup by specifying it in config/application.rb.

Include lib/error/error_handler.rb Module in application_controller.rb

Note: I am using a few Helper classes to render the json output. You can check it out here.

Before proceeding with the error_handler module here is a really interesting article on modules that you should definitely check out. If you notice the self.included method in a module works the same as if it is placed in the original class. So all we have to do is include the ErrorHandler module in ApplicationController.

users_controller.rb

Let’s refactor the ErrorModule to accommodate multiple error handling blocks. It looks much cleaner this way.

Refactored lib/error/error_handler.rb

If you notice ActiveRecord:RecordNotFound error also inherits StandardError. Since we have a rescue mechanism for it we get a :record_not_found. The StandardError block acts as a Fallback mechanism that handles all errors.

Define your own Exception.

We can also define our own Error classes that inherits from StandardError. To keep things simple we can create a CustomError class that holds the common variables and methods for all the user defined error classes. Now our UserDefinedError extends the CustomError.

lib/error/custom_error.rb and lib/error/not_visible_error.rb

We can override the methods specific to each Error. For example NotVisibleError extends CustomError. As you may notice we override the error_message.

NotVisibleError handled in ErrorModule

To handle all user defined Errors all we have to do is rescue from CustomError. We can also rescue from the specific Error if we want to handle it differently.

404 and 500

You can handle common exceptions like 404 and 500, although it’s totally up to the developer. We need to create a separate controller class, ErrorsController for it.

errors_controller.rb

Tell Rails to use Routes to resolve exceptions. We just have to add the following line to application.rb.

config/routes.rb

Now 404 exceptions fallback to errors#not_found and 500 to errors#internal_server_error.

Final Notes

The Modular approach is the Rails way of handling errors. Whenever we want to change a particular error message/format we just have to change it in one place. By this approach we also separate the Application logic from error handling thus making the Controllers Slick instead of Fat.

It’s best practice in Rails to have Skinny Controllers & Models.

Here is the complete Source code for handling errors with modular approach. Please do hit the Recommend button if you found it useful. And as always feel free to respond if you have any doubts. Cheers!

--

--