Otpor is a Rails engine designed to enhance the functionality of JSON responses and handle ActiveRecord validation errors in a more structured and consistent way.
- Custom JSON Responses: Handles the rendering of JSON responses with metadata, status codes, and errors.
- Exception Handling: Catches exceptions and gracefully returns error details in JSON format.
- ActiveRecord Validation Error Logging: Logs validation errors after they occur, providing structured error details for each failed validation.
- Request Store: Uses
RequestStoreto store request-specific data such as errors, status codes, and exception logs.
Add Otpor to your application's Gemfile:
gem 'otpor'And then execute:
$ bundle installOr install it yourself as:
$ gem install otporThe JsonResponse module provides a way to handle JSON responses for your Rails controllers. It captures the status code, renders partials if available, and includes metadata or pagination details when necessary.
In your controller:
class MyController < ApplicationController
include Otpor::JsonResponse
def show
# Your logic here
end
endThe engine will automatically render a structured JSON response based on the action's results. The following attributes are included:
- status: Includes the name, code, and type of the HTTP status.
- data: Renders partials if they exist for the specific controller action.
- errors: Any errors captured during the request.
- notes: Additional notes, if any.
- meta: Pagination and metadata for the response.
- exception_log: Exception details for development environments.
To render content inside the data field of the JSON response, you must create a partial with the same name as the controller action. For example, if you have an action named show, you should create a partial called _show.json.jbuilder in the relevant views directory.
Example:
# app/views/my_controller/_show.json.jbuilder
json.extract! @record, :id, :name, :descriptionWhen the action executes successfully, this partial will be rendered inside the data field of the JSON response.
Here is an example of a basic JSON response generated by Otpor when there are no errors and a partial exists:
{
"status": {
"name": "OK",
"code": 200,
"type": "Success"
},
"data": {
"id": 1,
"name": "Item 1",
"description": "This is a description."
},
"errors": null,
"notes": null,
"meta": null,
"exception_log": null
}If you are using Kaminari for pagination, Otpor will automatically populate the meta field in the JSON response with pagination metadata.
In your controller:
class MyController < ApplicationController
include Otpor::JsonResponse
def index
@items = Item.page(params[:page])
end
end{
"status": {
"name": "OK",
"code": 200,
"type": "Success"
},
"data": [
{ "id": 1, "name": "Item 1" },
{ "id": 2, "name": "Item 2" }
],
"errors": null,
"notes": null,
"meta": {
"pagination": {
"total_pages": 10,
"total_count": 100,
"current_page": 1,
"next_page": 2,
"prev_page": null,
"per_page": 10
}
},
"exception_log": null
}You can also set @pagination manually in your controller to override the automatic inference:
class MyController < ApplicationController
include Otpor::JsonResponse
def index
@pagination = {
pagination: {
total_pages: 5,
total_count: 50,
current_page: 2,
next_page: 3,
prev_page: 1,
per_page: 10
}
}
end
endIf you are building an API and need to version your responses, Otpor will automatically include the API version in the JSON response based on the URL pattern (e.g., /v1/, /v2/).
Your URL might look like this: http://example.com/api/v1/items.
Here is an example of a JSON response with API versioning:
{
"status": {
"name": "OK",
"code": 200,
"type": "Success"
},
"data": [
{ "id": 1, "name": "Item 1" },
{ "id": 2, "name": "Item 2" }
],
"errors": null,
"notes": null,
"meta": {
"api_version": "v1"
},
"exception_log": null
}You can also set @api_version manually in your controller to override the automatic inference:
class MyController < ApplicationController
include Otpor::JsonResponse
def index
@api_version = { api_version: "v2" }
end
endYou can set both @pagination and @api_version manually to have full control over the meta field:
class MyController < ApplicationController
include Otpor::JsonResponse
def index
@pagination = {
pagination: {
total_pages: 3,
total_count: 30,
current_page: 1,
next_page: 2,
prev_page: nil,
per_page: 10
}
}
@api_version = { api_version: "v3" }
end
endThis will produce a response with both custom values in the meta field:
{
"status": {
"name": "OK",
"code": 200,
"type": "Success"
},
"data": [...],
"errors": null,
"notes": null,
"meta": {
"pagination": {
"total_pages": 3,
"total_count": 30,
"current_page": 1,
"next_page": 2,
"prev_page": null,
"per_page": 10
},
"api_version": "v3"
},
"exception_log": null
}The ActiveRecordValidationError module logs all validation errors after they occur and stores them in the request for easy access. This module is intended to be included in controllers to handle validation errors from ActiveRecord.
In your controller:
class MyController < ApplicationController
include Otpor::ActiveRecordValidationError
def create
# Your logic here
@record = MyModel.new(record_params)
if @record.save
# success logic
else
# validation errors will be captured automatically
end
end
endWhenever validation errors occur, they will be captured and stored in RequestStore, which can then be accessed and returned in the JSON response.
{
"status": {
"name": "Unprocessable Entity",
"code": 422,
"type": "Client Error"
},
"data": null,
"errors": [
{
"attribute": "name",
"message": "can't be blank",
"type": "validation",
"entity": "MyModel",
"entity_id": "new record"
}
],
"notes": null,
"meta": null,
"exception_log": null
}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- Fork the project;
- Make the necessary adjustments or improvements along with the corresponding tests;
- Submit a pull request;
This gem is available as open source under the terms of the MIT License.