JSONAPI.rb

repository·master·Indexed 42 days ago

https://github.com/stas/jsonapi.rb

A collection of Ruby modules and mixins for Rails developers to implement the JSON:API specification with minimal boilerplate. It provides tools for serialization, deserialization, pagination, filtering via Ransack, and automated error handling for ActiveRecord and ActionController exceptions.

Tokens
6K
Snippets
21
Records
28
Agent score
54%

What's inside JSONAPI.rb

  1. Enable JSONAPI.rb support for Rails

    master

    To integrate JSONAPI.rb with Rails, you must register the appropriate mime types and renderers (jsonapi and jsonapi_errors) by adding an initializer:

    # config/initializers/jsonapi.rb
    require 'jsonapi'
    
    JSONAPI::Rails.install!
  2. Filter and sort resource collections using Ransack syntax

    master

    JSONAPI.rb provides filtering and sorting support that leverages Ransack specifications. This allows you to pass complex query parameters via the URL to filter and order ActiveRecord collections.

    Query Syntax

    • Filtering: Use the filter parameter. Fields can include predicates (e.g., matches_any).
    • Sorting: Use the sort parameter. Prefix a field with - for descending order, otherwise it defaults to ascending.

    Example Request

    GET /resource?filter[region_matches_any]=Lisb%&sort=-created_at,id

  3. Configure custom serializer resolution in Rails

    master

    By default, JSONAPI::Rails resolves a serializer class by appending Serializer to the class name of the resource (e.g., User becomes UserSerializer).

    If you need to override this behavior, you can define a jsonapi_serializer_class method in your controller. The renderer checks for this method before falling back to the default JSONAPI::Rails.serializer_class logic.

  4. Remap the root key to `errors`

    master

    The ErrorSerializer automatically overrides serializable_hash to ensure the JSON:API response uses the errors root key. It maps the contents of the :data array to the :attributes of each error object in the resulting hash.

    Expected output structure:

    {
      "errors": [
        { "status": "422", "detail": "Invalid attribute" }
      ]
    }
  5. Install JSONAPI.rb in Rails

    master

    To integrate JSONAPI.rb with a Rails application, call JSONAPI::Rails.install!. This method performs the following setup:

    1. Registers the :jsonapi MIME type.
    2. Maps the JSON parser to jsonapi requests (ensuring params are parsed correctly for JSONAPI content-type).
    3. Adds the :jsonapi renderer to ActionController.
    4. Adds the :jsonapi_errors renderer to ActionController.

    Note: This method checks if ::Rails is defined before proceeding.

    # In a Rails initializer (e.g., config/initializers/jsonapi.rb)
    JSONAPI::Rails.install!
  6. Handle JSON:API error responses in Rails controllers

    master

    Include the JSONAPI::Errors module in your Rails controllers to automatically catch common exceptions and render them as compliant JSON:API error responses.

    When included, the module uses rescue_from to register handlers for:

    • StandardError: Renders a 500 Internal Server Error.
    • ActiveRecord::RecordNotFound: Renders a 404 Not Found (if ActiveRecord is defined).
    • ActionController::ParameterMissing: Renders a 422 Unprocessable Entity (if ActionController is defined).

    Note: In Rails test environments, StandardError handling is skipped to allow standard test exception reporting.

    class ApplicationController < ActionController::Base
      include JSONAPI::Errors
    end
  7. Handle errors with `JSONAPI::Errors`

    master

    Include JSONAPI::Errors to automatically handle exceptions from strong parameters, generic errors, or record-not-found errors. To render validation errors, use the jsonapi_errors renderer.

    To integrate an exception notifier (like Raven), override render_jsonapi_internal_server_error(exception):

    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
  8. Add collection meta information

    master

    To include metadata (like total counts) in your JSON:API collection responses, implement the jsonapi_meta(resources) method in your controller:

    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
  9. Filter and sort data with `JSONAPI::Filtering`

    master

    The JSONAPI::Filtering module uses Ransack to provide filtering and sorting capabilities. Note: You must add ransack to your Gemfile to use this.

    Basic Filtering and Sorting

    Implement jsonapi_filter in your controller:

    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 supports queries like: curl -X GET /api/resources?filter[model_attr_or_relationship_attr_cont_any]=value,name&sort=-model_attr,relationship_attr

    Sorting with Expressions

    To use SQL aggregations (e.g., min, max, avg, sum, count) for sorting, enable the sort_with_expressions option:

    options = { sort_with_expressions: true }
    jsonapi_filter(User.all, allowed_fields, options) do |filtered|
      render jsonapi: filtered.result.group('id').to_a
    end

    This enables queries like: curl -X GET /api/resources?sort=-model_attr_sum

  10. Deserialize JSON:API documents with `JSONAPI::Deserialization`

    master

    The jsonapi_deserialize helper transforms a JSON:API document into a flat dictionary suitable for updating an ActiveRecord::Base model.

    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

    Deserialization Options

    • only: Returns exclusively attributes/relationship data in the provided list.
    • except: Returns exclusively attributes/relationship NOT in the provided list.
    • polymorphic: Adds and detects the _type attribute and class for defined polymorphic relationships.

    Note: This requires inflections. If you are not using Rails/ActiveSupport, ensure you have the dry/inflector gem installed.

  11. Implement Pagination with `JSONAPI::Pagination`

    master

    Include JSONAPI::Pagination to support paginating model record sets. This automatically generates relevant pagination _links.

    Basic Usage

    class MyController < ActionController::Base
      include JSONAPI::Pagination
    
      def index
        jsonapi_paginate(Model.all) do |paginated|
          render jsonapi: paginated
        end
      end
    end

    Customizing Pagination Meta

    To add pagination info to your collection's meta, use jsonapi_pagination_meta inside your jsonapi_meta method:

      def jsonapi_meta(resources)
        pagination = jsonapi_pagination_meta(resources)
        { pagination: pagination } if pagination.present?
      end

    Customizing Page Size

    To change the default items per page or implement custom logic, implement jsonapi_page_size(pagination_params):

      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