Module to build response object for Rails 5 API Applications.
Depends on active_model_serializers gem.
Pass a valid / invalid ActiveRecord object or an instance of ActiveRecord::Relation and get response object in following structure.
| Properties | Description |
|---|---|
| status | 'failure' (or) 'success' |
| body | Serialized Object |
| messages | Error Description (if any) |
| status_code | HTTP Status Code |
#app/controllers/api/v1/application_controller.rb module Api module V1 class ApplicationController < ActionController::API def serializer_responder(resource, config = {}) response = ::Api::ResponseBuilder::Main.new(resource, config, params).response render json: response, status: response[:status_code] end end end end # app/serializers/v1/user_serializer.rb module V1 # Serializer for User model class UserSerializer < ::ActiveModel::Serializer attributes :id, :firstname, :lastname, :phone_number, :email end end # app/controllers/api/v1/users_controller.rb module Api module V1 # Defines endpoints for CRUD operations on user model class UsersController < ::Api::V1::ApplicationController def index users = User.all serializer_responder(users, serializer: ::V1::UserSerializer) end end end end
Response Object for API endpoint /api/v1/users will be
{
"status": "success",
"body" :
[
{
"id": 1,
"firstname": "Kalidas",
"lastname": "M",
"phone_number": "+919876543210",
"email": "kalidasm610@gmail.com"
},
{
"id": 2,
"firstname": "Dass",
"lastname": "Mk",
"phone_number": "+919876543211",
"email": ""
}
],
"status_code": "ok"
}Add this line to your application's Gemfile:
gem 'api-response_builder'
And then execute:
$ bundle
Or install it yourself as:
$ gem install api-response_builder
By taking advantage of Ruby's inheritance and Rails's app structure, few instance methods in application controller can be used to handle all scenarios for rendering json response across entire application.
module Api module V1 class ApplicationController < ActionController::API # action callbacks # Global Exception Handler for Api Exceptions and StandardError rescue_from ::Api::Exception, StandardError do |e| handle_api_exception(e) end # Global exception handlers for ActiveRecord Exceptions rescue_from ::ActiveRecord::RecordNotFound, with: :render_record_not_found rescue_from ::ActiveRecord::RecordInvalid, with: :render_record_invalid rescue_from ::ActiveRecord::RecordNotDestroyed, with: :render_forbidden # Public methods def handle_api_exception(e) response = ::Api::ResponseBuilder::Main.new(e, {}, params).response render json: response, status: response[:status_code] end def render_record_not_found handle_api_exception ::Api::Exception.new( ::Api::Exception.record_not_found ) end def render_internal_server_error handle_api_exception ::Api::Exception.new( ::Api::Exception.internal_server_error ) end def render_record_invalid(e) # If exception message is not included in arguments, locale message # (if present) for record invalid exception will be returned in resp exception_message = e.record.errors.full_messages exception = ::Api::Exception.new( ::Api::Exception.record_invalid, exception_message ) handle_api_exception(exception) end def render_forbidden handle_api_exception ::Api::Exception.new( ::Api::Exception.forbidden_resource ) end def serializer_responder(resource, config = {}) response = ::Api::ResponseBuilder::Main.new(resource, config, params).response # response object also contains corresponding http status code under the key :status_code # Including `Http Status Code` as part of API response is generally considered as good practice # If all your api responses should have `200 OK` as status code, omit status key in render method # Rails, by default, set status code as `200 OK` render json: response, status: response[:status_code] end end end end
In some cases, meta information may be passed along with response. For example, passing total count of resources for pagination.
In order to pass meta info, include :meta key-value pair in config object passed to ::Api::ResponseBuilder::Main.
It will be reflected in response object under meta key
Note:
- config[:serializer] should be
ActiveModel::Serializerclass (if serializer is not passed, it will serialize entire object) config[:meta]will accept only hash
# app/controllers/api/v1/users_controller.rb def index users = User.all config = {} config[:serializer] = UserSerializer config[:meta] = { total_count: users.count } serializer_responder(users, config) end # simple way serializer_responder(users, { serializer: UserSerializer, meta: { total_count: users.count } })
{
"status": "success",
"body" :
[
{
"id": 1,
"firstname": "Kalidas",
"lastname": "M",
"phone_number": "+919876543210",
"email": "kalidasm610@gmail.com"
},
{
"id": 2,
"firstname": "Dass",
"lastname": "Mk",
"phone_number": "+919876543211",
"email": ""
}
],
"status_code": "ok",
"meta": {
"total_count": 2
}
}Following exceptions are handled by this gem as there are the most commonly used.
| EXCEPTION | HTTP Status Code |
|---|---|
| INTERNAL_SERVER_ERROR | :internal_server_error (500) |
| RECORD_NOT_FOUND | :not_found (404) |
| RECORD_INVALID | ::unprocessable_entity (422) |
| RECORD_NOT_DESTROYED | :forbidden (403) |
| FORBIDDEN_RESOURCE | :forbidden (403) |
| UNAUTHORIZED_ACCESS | :unauthorized (401) |
For detailed information, feel free to dive into the Code
All the error messages are internalized using Rails Internationalization (I18n) API.
Exceptional messages are mapped under I18n locales (en.api_response.messages.#{key}).
Key represents lower-cased version of exception names listed above.
Example :
en: api_response: messages: internal_server_error: "Internal server error." record_not_found: "The resource you are looking for does not exist." record_invalid: "Validation Failed" record_not_destroyed: "Failed to delete the resource" forbidden_resource: "You don't have sufficient privileges to perform this operation" unauthorized_access: "You are not authorized to perform this operation"
class CreditsController < ApplicationController before_action :authorize_user, only: :update def update @credit.update(credit_params) end private def authorize_user return if user_authorized? # Will be handled by rescue_from methods in application controller raise ::Api::Exception.new( ::Api::Exception.unauthorized_access ) end end
Response would be
{
"status": "failure",
"messages": {
"errors": ["You are not authorized to perform this operation"]
},
"status_code": "unprocessable_entity",
"meta": {}
}If you want to override the default error message in a specific scenario,
private def authorize_user return if user_authorized? raise ::Api::Exception.new( ::Api::Exception.unauthorized_access, "Looks like you are not authorized to update credit score" ) end
{
"status": "failure",
"messages": {
"errors": ["Looks like you are not authorized to update credit score"]
},
"status_code": "unprocessable_entity",
"meta": {}
}Response Object contains a key named status. It will have only any one of following types
- failure
- success
Status will be failure when
- Resource has any errors
- Resource is an instance of
::Api::Exception - StandardError is raised
Status will be success when
- Resource do not have any errors
- Resource is not an instance of
::Api::Exception - No Exception been raised
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/kalidasm/api-response_builder.
The gem is available as open source under the terms of the MIT License.