A couple of months ago, we started a new project and decided to use Ember on the front and Rails as an API backend.
Early in development, we found ourselves repeating similar code in multiple places in our controllers.
def create user = User.new(user_params) if user.save render json: user, serializer: UserSerializer, status: 201 else render error: user.errors, serializer: ErrorSerializer, status: 422 end end
Not just the create method, but generally, our controller started to have a lot of boilerplate code. Another problem was handling status codes. JSON API standard has strict policies on status codes for each response type, i.e create(201), update(200), error(422) etc.
Handling status codes manually and having a lot of boilerplate code makes the entire process error-prone, and naturally, we wanted to avoid that.
Two gems could potentially make our lives easier.
The first one was RocketPants which we used in our previous projects and we liked it. However, RocketPants isn’t compatible with the new version of ActiveModelSerializers and the last commit on RocketPants was two years ago so the project looked abandoned.
The second gem that could potentially handle our problems was Grape. It’s great but it would be a full replacement for Rails, and we didn’t want to make a tradeoff just like that.
In the beginning, the method only handled status codes based on the action name. In our controllers, we only use the default CRUD action names such as index, show, create, update and destroy, so we could easily determine which status code to return.
Any other action would lead to the creation of a dedicated controller (which itself only has default CRUD actions). For quite some time we have been managing our controllers like this and we were happy when we found out that DHH does it the same way.
We moved our logic of determining whether the object is valid and which HTTP status code to return into our
respond_with method, so our controller actions started to be easy one-liners:
def create respond_with User.create(user_params), serializer: UserSerializer end
Now our controllers started to look much cleaner and more readable and we wanted to bring it a step further – which brings us to error handling.
One of the built-in features of JsonApiResponders is the ability to handle rescuing exceptions. At this point, it will rescue
ActionController::ParameterMissing errors which we believe are the most occurring ones.
def show respond_with User.find(params[:id]), serializer: UserSerializer end
If the given user id does not exist, JsonApiResponders will catch the error and return a JSON API compliant response with the appropriate error message.
ActionController::ParameterMissing will also be handled automatically in the following way.
def create respond_with User.new(user_params), serializer: UserSerializer end
If there is no
user param sent in the request, ActionController will raise an error here, which JsonApiResponders will handle automatically.
JsonApiResponders also supports responding with custom errors. You can use the
respond_with_error method to handle any kind of errors. This method will render an error message as described in the JSON API specification. Below you can see an example of how it can be used with the Pundit gem:
rescue_from Pundit::NotAuthorizedError do respond_with_error(403, ’Not Authorized’) end
JsonApiResponders is compatible with both ActiveModelSerializers and JsonapiRails gems. If you are using ActiveModelSerializers you don’t need to configure anything, it will work out of the box. However, if you are using JsonapiRails gem you need to modify
render_method option in JsonApiResponders configuration. The configuration will most likely be located in
JsonApiResponders.configure do |config| config.render_method = :jsonapi end
After you make this change, you are good to go!
We’ve been using our JsonApiResponders on multiple projects and are really satisfied with the results. We don’t have to handle and think about response codes or whether we’re compliant with the JSON API standard. Everything is handled automatically! We don’t have if clauses on create and update methods to check for errors, and there is also simple error handling for record not found and missing parameters.
All of this makes our controllers much cleaner and readable. In most cases, the response will be a one-liner. All in the comfort of Rails Controllers.