So you say you need JSON:API support in your API...
- hey how did your hackathon go?
- not too bad, we got Babel set up
- yep…
- yep.
Here are some codes to help you build your next JSON:API compliable application easier and faster.
But why?
It's quite a hassle to setup a Ruby (Rails) web application to use and follow the JSON:API specifications.
The idea is simple, JSONAPI.rb offers a bunch of modules/mixins/glue, add them to your controllers, call some methods, profit!
Main goals:
- No magic please
- No DSLs please
- Less code, less maintenance
- Good docs and test coverage
- Keep it up-to-date (or at least tell people this is for grabs)
The available features include:
- object serialization (powered by JSON:API Serializer, was
fast_jsonapi) - error handling (parameters, validation, generic errors)
- fetching of the data (support for includes and sparse fields)
- filtering and sorting of the data (powered by Ransack, soft-dependency)
- pagination support
But how?
Mainly by leveraging JSON:API Serializer and Ransack.
Thanks to everyone who worked on these amazing projects!
Sponsors
I'm grateful for the following companies for supporting this project!
Installation
Add this line to your application's Gemfile:
And then execute:
Or install it yourself as:
Usage
- Object serialization
- Collection meta
- Error handling
- Includes and sparse fields
- Filtering and sorting
- Pagination
- Deserialization
To enable the support for Rails, add this to an initializer:
# config/initializers/jsonapi.rb require 'jsonapi' JSONAPI::Rails.install!
This will register the mime type and the jsonapi and jsonapi_errors
renderers.
Object serialization
The jsonapi renderer will try to guess and resolve the serializer class based
on the object class, and if it is a collection, based on the first item in the
collection.
The naming scheme follows the ModuleName::ClassNameSerializer for an instance
of the ModuleName::ClassName.
Please follow the JSON:API Serializer guide on how to define a serializer.
To provide a different naming scheme implement the jsonapi_serializer_class
method in your resource or application controller.
Here's an example:
class CustomNamingController < ActionController::Base # ... private def jsonapi_serializer_class(resource, is_collection) JSONAPI::Rails.serializer_class(resource, is_collection) rescue NameError # your serializer class naming implementation end end
To provide extra parameters to the serializer,
implement the jsonapi_serializer_params method.
Here's an example:
class CustomSerializerParamsController < ActionController::Base # ... private def jsonapi_serializer_params { first_name_upcase: params[:upcase].present? } end end
Collection meta
To provide meta information for a collection, provide the jsonapi_meta
controller method.
Here's an example:
class MyController < ActionController::Base def index render jsonapi: Model.all end private def jsonapi_meta(resources) { total: resources.count } if resources.respond_to?(:count) end end
Error handling
JSONAPI::Errors provides a basic error handling. It will generate a valid
error response on exceptions from strong parameters, on generic errors or
when a record is not found.
To render the validation errors, just pass it to the error renderer.
To use an exception notifier, overwrite the
render_jsonapi_internal_server_error method in your controller.
Here's an example:
class MyController < ActionController::Base include JSONAPI::Errors def update record = Model.find(params[:id]) if record.update(params.require(:data).require(:attributes).permit!) render jsonapi: record else render jsonapi_errors: record.errors, status: :unprocessable_entity end end private def render_jsonapi_internal_server_error(exception) # Call your exception notifier here. Example: # Raven.capture_exception(exception) super(exception) end end
Includes and sparse fields
JSONAPI::Fetching provides support on inclusion of related resources and
serialization of only specific fields.
Here's an example:
class MyController < ActionController::Base include JSONAPI::Fetching def index render jsonapi: Model.all end private # Overwrite/whitelist the includes def jsonapi_include super & ['wanted_attribute'] end end
This allows you to run queries like:
$ curl -X GET /api/resources?fields[model]=model_attr,relationshipFiltering and sorting
JSONAPI::Filtering uses the power of
Ransack
to filter and sort over a collection of records.
The support is pretty extended and covers also relationships and composite
matchers.
Please add ransack to your Gemfile in order to benefit from this functionality!
Here's an example:
class MyController < ActionController::Base include JSONAPI::Filtering def index allowed = [:model_attr, :relationship_attr] jsonapi_filter(Model.all, allowed) do |filtered| render jsonapi: filtered.result end end end
This allows you to run queries like:
$ curl -X GET \ /api/resources?filter[model_attr_or_relationship_attr_cont_any]=value,name\ &sort=-model_attr,relationship_attr
Sorting using expressions
You can use basic aggregations like min, max, avg, sum and count
when sorting. This is an optional feature since SQL aggregations require
grouping. To enable expressions along with filters, use the option flags:
options = { sort_with_expressions: true } jsonapi_filter(User.all, allowed_fields, options) do |filtered| render jsonapi: filtered.result.group('id').to_a end
This allows you to run queries like:
$ curl -X GET /api/resources?sort=-model_attr_sumPagination
JSONAPI::Pagination provides support for paginating model record sets as long
as enumerables.
Here's an example:
class MyController < ActionController::Base include JSONAPI::Pagination def index jsonapi_paginate(Model.all) do |paginated| render jsonapi: paginated end end end
This will generate the relevant pagination links.
If you want to add the pagination information to your meta,
use the jsonapi_pagination_meta method:
def jsonapi_meta(resources) pagination = jsonapi_pagination_meta(resources) { pagination: pagination } if pagination.present? end
If you want to change the default number of items per page or define a custom logic to handle page size, use the
jsonapi_page_size method:
def jsonapi_page_size(pagination_params) per_page = pagination_params[:size].to_f.to_i per_page = 30 if per_page > 30 || per_page < 1 per_page end
Deserialization
JSONAPI::Deserialization provides a helper to transform a JSONAPI document
into a flat dictionary that can be used to update an ActiveRecord::Base model.
Here's an example using the jsonapi_deserialize helper:
class MyController < ActionController::Base include JSONAPI::Deserialization def update model = MyModel.find(params[:id]) if model.update(jsonapi_deserialize(params, only: [:attr1, :rel_one])) render jsonapi: model else render jsonapi_errors: model.errors, status: :unprocessable_entity end end end
The jsonapi_deserialize helper accepts the following options:
only: returns exclusively attributes/relationship data in the provided listexcept: returns exclusively attributes/relationship which are not in the listpolymorphic: will add and detect the_typeattribute and class to the defined list of polymorphic relationships
This functionality requires support for inflections. If your project uses
active_support or rails you don't need to do anything. Alternatively, we will
try to load a lightweight alternative to active_support/inflector provided
by the dry/inflector gem, please make sure it's added if you want to benefit
from this feature.
Development
After checking out the repo, run bundle to install dependencies.
Then, run rake spec to run the tests.
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.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/stas/jsonapi.rb
This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.
