jsonapi-realizer
This library handles incoming json:api payloads and turns them, via an adapter system, into data models for your business logic.
Using
In order to use this library you'll want to have some models:
class Photo < ApplicationRecord
belongs_to :photographer, class_name: "Profile"
end
class Profile < ApplicationRecord
has_many :photos
end
Note: They don't have to be ActiveRecord models, but we have built-in support for that library (adapter-based).
Second you'll need some realizers:
class PhotoRealizer
include JSONAPI::Realizer::Resource
register :photos, class_name: "Photo", adapter: :active_record
has_one :photographer, as: :profiles
has :title
has :src
end
class ProfileRealizer
include JSONAPI::Realizer::Resource
register :profiles, class_name: "Profile", adapter: :active_record
has_many :photos
has :name
end
You can define aliases for these properties:
has_many :doctors, as: :users
has :title, as: :name
Once you've designed your resources, we just need to use them! In this example, we'll use controllers from Rails:
class PhotosController < ApplicationController
def create
realization = JSONAPI::Realizer.create(params, headers: request.headers)
ProcessPhotosService.new(realization.model)
render json: JSONAPI::Serializer.serialize(realization.model)
end
def index
realization = JSONAPI::Realizer.index(params, headers: request.headers, type: :photos)
render json: JSONAPI::Serializer.serialize(realization.models, is_collection: true)
end
end
Notice that we pass realization.model
to ProcessPhotosService
, that's because jsonapi-realizer
doesn't do the act of saving, creating, or destroying! We just ready up the records for you to handle (including errors).
Adapters
There are two core adapters:
:active_record
, which assumes an ActiveRecord-like interface.:memory
, which assumes aSTORE
Hash-like on the model class.
An adapter must provide the following interfaces:
find_one
, describes how to find the modelfind_many
, describes how to find many modelswrite_attributes
, describes how to write a set of propertieswrite_relationships
, describes how to write a set of relationshipsincludes_via
, describes how to eager include related modelssparse_fields
, describes how to only return certain fields
You can also provide custom adapter interfaces like below, which will use active_record
's find_many
, write_relationships
, update_via
, includes_via
, and sparse_fields
:
class PhotoRealizer
include JSONAPI::Realizer::Resource
register :photos, class_name: "Photo", adapter: :active_record
adapter.find_one do |model_class, id|
model_class.where { id == id or slug == id }.first
end
adapter.write_attributes do |model, attributes|
model.update_columns(attributes)
end
has_one :photographer, as: :profiles
has :title
has :src
end
rails
If you want to use jsonapi-realizer in development mode you'll want to turn on eager_loading
(by setting it to true
in config/environments/development.rb
) or by adding app/realizers
to the eager_load_paths
.
rails and pundit and jsonapi-serializers
While this gem contains nothing specifically targeting rails or pundit or jsonapi-serializers (a fantastic gem) I've already written some seamless integration code. This root controller will handle exceptions in a graceful way and also give you access to a clean interface for serializing:
module V1
class ApplicationController < ::ApplicationController
include Pundit
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
rescue_from JSONAPI::Realizer::Error::MissingAcceptHeader, with: :missing_accept_header
rescue_from JSONAPI::Realizer::Error::InvalidAcceptHeader, with: :invalid_accept_header
rescue_from Pundit::NotAuthorizedError, with: :access_not_authorized
private def missing_accept_header
head :not_acceptable
end
private def invalid_accept_header
head :not_acceptable
end
private def
head :unauthorized
end
private def pundit_user
current_account
end
private def serialize(realization)
JSONAPI::Serializer.serialize(
if realization.respond_to?(:models) then realization.models else realization.model end,
is_collection: realization.respond_to?(:models),
meta: ,
links: serialized_links,
jsonapi: serialized_jsonapi,
fields: serialized_fields(realization),
include: serialized_includes(realization),
namespace: ::V1
)
end
private def
{
api: {
version: "1"
}
}
end
private def serialized_links
{
discovery: {
href: "/"
}
}
end
private def serialized_jsonapi
{
version: "1.0"
}
end
private def serialized_fields(realization)
realization.fields if realization.fields.any?
end
private def serialized_includes(realization)
realization.includes if realization.includes.any?
end
end
end
You can see this resource controller used below:
module V1
class AccountsController < ::V1::ApplicationController
def index
realization = JSONAPI::Realizer.index(
policy(Account).sanitize(:index, params),
headers: request.headers,
scope: policy_scope(Account),
type: :accounts
)
realization.relation
render json: serialize(realization)
end
def create
realization = JSONAPI::Realizer.create(
policy(Account).sanitize(:create, params),
headers: request.headers,
scope: policy_scope(Account)
)
realization.relation
render json: serialize(realization)
end
end
end
jsonapi-home
I'm already using jsonapi-realizer and it's sister project jsonapi-serializers in a new gem of mine that allows services to be discoverable: jsonapi-home.
Installing
Add this line to your application's Gemfile:
$ bundle add jsonapi-realizer
Or install it yourself with:
$ gem install jsonapi-realizer
Contributing
- Read the Code of Conduct
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request