jsonapi-realizer

  • Build
  • Downloads
  • Version

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:

  1. :active_record, which assumes an ActiveRecord-like interface.
  2. :memory, which assumes a STORE Hash-like on the model class.

An adapter must provide the following interfaces:

  1. find_one, describes how to find the model
  2. find_many, describes how to find many models
  3. write_attributes, describes how to write a set of properties
  4. write_relationships, describes how to write a set of relationships
  5. includes_via, describes how to eager include related models
  6. sparse_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 access_not_authorized
      head :unauthorized
    end

    private def pundit_user
      
    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
      )

      authorize 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)
      )

      authorize 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

  1. Read the Code of Conduct
  2. Fork it
  3. Create your feature branch (git checkout -b my-new-feature)
  4. Commit your changes (git commit -am 'Add some feature')
  5. Push to the branch (git push origin my-new-feature)
  6. Create new Pull Request