When some time ago I came with an advanced way of handling exceptions in Ruby Applications I realized, that this is a wider problem than I thought. A lot of people found the idea very useful and therefore I decided to go with this topic even further.
I strongly encourage you to check my article about the greater way of handling Errors in Rails Web applications using "dry-monads"! You can integrate jsonapi_errors_handler
easily with Endpoints abstraction to make your code even more clean.
JSON API Errors Handler
The natural follow-up is a JsonapiErrorsHandler gem. This is a small, open-source package that you can hook into any kind of Ruby or Rails application and it helps you handle API errors in the really convenient way.
How does it work?
The usual error handling in rails applications is well described in my previous post, so let's focus here on how the gem's behavior makes it easier for you to work with API errors.
Ruby On Rails REST API
The complete guide
Create professional API applications that you can hook anything into! Learn how to code like professionals using Test Driven Development!
Take this course!The main idea is to support JSON API standard for your API communication. Whenever you'll raise an error in your application while processing the HTTP request, the JsonapiErrorsHandler will handle that fact and remove the overhead from you by delivering the well-formatted response to the client.
JsonapiErrorsHandler gem's concept
As you can see, it's fairly simple. The idea is just to remove the responsibility to recognize serializable errors and the logic of translating it into JsonAPI-friendly format out of your application.
Using JsonapiErrorsHandler gem.
To use the gem in the rails application, just go to your base API controller, and include those two lines at the top:
# app/controllers/api/application_controller.rb
...
include JsonapiErrorsHandler
rescue_from ::StandardError, with: lambda { |e| handle_error(e) }
...
By default, gem defines 5 types of errors that, when risen, are handled and transformed out of the box:
- _JsonapiErrorsHandler::Errors::Unauthorized_ => status: 401
- _JsonapiErrorsHandler::Errors::Forbidden_ => status: 403
- _JsonapiErrorsHandler::Errors::NotFound_ => status: 404
- _JsonapiErrorsHandler::Errors::Invalid_ => status: 422
- _JsonapiErrorsHandler::Errors::StandardError_ => status: 500
If you want to return a friendly error message, just raise any of the errors above in your controller.
def show
raise JsonapiErrorsHandler::Errors::NotFound unless ... # do the fancy find
end
That's already looking pretty cool, but we can do even better.
JSON API Error Mapper
in rails applications, a very common way to find resources is by using the find method. If you do so, in case of failure, there is already a default Actvierecord::RecordNotFound exception risen. In such cases, Our code would not be too pretty, would it?
def show
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
raise JsonapiErrorsHandler::Errors::NotFound
end
In such cases, the custom ErrorMapper comes handy.
In JsonapiErrorsHandler you can have ANY error being mapped into any API-friendly error. Let's refactor our code above to make use of this feature.
# app/controllers/api/application_controller.rb
...
include JsonapiErrorsHandler
JsonapiErrorMapper.map_errors!({
'ActiveRecord::RecordNotFound' => 'JsonapiErrorsHandler::Errors::NotFound'
})
rescue_from ::StandardError, with: lambda { |e| handle_error(e) }
...
Here in the application controller after including the JsonapiErrorsHandler gem we added an error mapping call. This way we informed the gem that whenever the error of a class specified in the key of the mapper is being risen, handle it using an instance of a class specified as a value.
This allows us to completely get rid of error's handling overhead from all controllers in our application:
def show
@user = User.find(params[:id])
end
Using JsonapiErrorsHandler in non-rails applications
If you'll look into the gem's repository, currently there is only one line that kind of dependents of rails, and it's a method: render_error:
def render_error(error)
render json: ::JsonapiErrorsHandler::ErrorSerializer.new(error), status: error.status end
Here we use render function which is rails-specific. If you want to use the gem in Sinatra, Hanami, or whatever else, just be sure you take this fact into the account. Either:
- Override a render_error method in the class that includes the module
- implement the render method, so the including class will have this accessible.
Drawbacks
Help welcome
Currently, this gem was tested only with Rails applications, but it's designed in the way there should not be too much rails dependencies involved. There is still room to improve So ANY contributions are very welcome!
Next steps
For this gem, I'll work on mutation test coverage, improved code quality and reduce the dependency of any particular framework. Also, I plan to come with a few more useful API gems so If you have any annoying problems in your Ruby applications, let me know in the comments.
Special thanks
- Tamara Bellis for a great cover photo.
- Nguyễn Trịnh Hồng Ngọc for a very early contribution to the gem and overall support.
- All students of the Ruby on Rails REST API course who helped to make this gem real.