RestPack / restpack_serializer

Model serialization, paging, side-loading and filtering
MIT License
175 stars 43 forks source link

restpack_serializer

Build Status Code Climate Dependency Status Gem Version Coverage Status

Model serialization, paging, side-loading and filtering

restpack_serializer allows you to quickly provide a set of RESTful endpoints for your application. It is an implementation of the emerging JSON API standard.

Live Demo of RestPack Serializer


NOTE: This gem needs maintainers: https://github.com/RestPack/restpack_serializer/issues/128


Getting Started

For rails projects:

After adding the gem restpack_serializer to your Gemfile, add this code to config/initializers/restpack_serializer.rb:

Dir[Rails.root.join('app/serializers/**/*.rb')].each do |path|
  require path
end

Serialization

Let's say we have an Album model:

class Album < ActiveRecord::Base
  attr_accessible :title, :year, :artist

  belongs_to :artist
  has_many :songs
end

restpack_serializer allows us to define a corresponding serializer:

class AlbumSerializer
  include RestPack::Serializer
  attributes :id, :title, :year, :artist_id, :href
end

AlbumSerializer.as_json(album) produces:

{
  "id": "1",
  "title": "Kid A",
  "year": 2000,
  "artist_id": 1,
  "href": "/albums/1"
}

as_json accepts an optional context hash parameter which can be used by your Serializers to customize their output:

class AlbumSerializer
  include RestPack::Serializer
  attributes :id, :title, :year, :artist_id, :extras
  optional :score

  can_include :artists, :songs
  can_filter_by :year

  def extras
    if @context[:admin?]
      { markup_percent: 95 }
    end
  end
end
AlbumSerializer.as_json(album, { admin?: true })

All attributes are serialized by default. If you'd like to skip an attribute, you can pass an option in the @context as follows:

AlbumSerializer.as_json(album, { include_title?: false })

You can also define optional attributes which aren't included by default. To include:

AlbumSerializer.as_json(album, { include_score?: true })

Exposing an API

The AlbumSerializer provides page and resource methods which provide paged collection and singular resource GET endpoints.

class AlbumsController < ApplicationController
  def index
    render json: AlbumSerializer.page(params)
  end

  def show
    render json: AlbumSerializer.resource(params)
  end
end

These endpoint will live at URLs such as /albums and /albums/142857:

The AlbumSerializer also provides a single method which will return a serialized resource similar to as_json above.

page, resource and single methods take an optional scope argument allowing us to enforce arbitrary constraints:

AlbumSerializer.page(params, Albums.where("year < 1950"))

In addition to scope, all three methods also accept an optional context hash:

AlbumSerializer.page(params, Albums.where("year < 1950"), { admin?: true })

Other features:

Paging

Collections are paged by default. page and page_size parameters are available:

Paging details are included in a meta attribute:

http://restpack-serializer-sample.herokuapp.com/api/v1/songs.json?page=2&page_size=3 yields:

{
    "songs": [
        {
            "id": "4",
            "title": "How to Dissapear Completely",
            "href": "/songs/4",
            "links": {
                "artist": "1",
                "album": "1"
            }
        },
        {
            "id": "5",
            "title": "Treefingers",
            "href": "/songs/5",
            "links": {
                "artist": "1",
                "album": "1"
            }
        },
        {
            "id": "6",
            "title": "Optimistic",
            "href": "/songs/6",
            "links": {
                "artist": "1",
                "album": "1"
            }
        }
    ],
    "meta": {
        "songs": {
            "page": 2,
            "page_size": 3,
            "count": 42,
            "include": [],
            "page_count": 14,
            "previous_page": 1,
            "next_page": 3,
            "first_href": "/songs?page_size=3",
            "previous_href": "/songs?page_size=3",
            "next_href": "/songs?page=3&page_size=3",
            "last_href": "/songs?page=14&page_size=3"
        }
    },
    "links": {
        "songs.artist": {
            "href": "/artists/{songs.artist}",
            "type": "artists"
        },
        "songs.album": {
            "href": "/albums/{songs.album}",
            "type": "albums"
        }
    }
}

URL Templates to related data are included in the links element. These can be used to construct URLs such as:

Side-loading

Side-loading allows related resources to be optionally included in a single API response. Valid side-loads can be defined in Serializers by using can_include as follows:

class AlbumSerializer
  include RestPack::Serializer
  attributes :id, :title, :year, :artist_id, :href

  can_include :songs, :artists
end

In this example, we are allowing related songs and artists to be included in API responses. Side-loads can be specifed by using the include parameter:

No side-loads

Side-load related Artists

which yields:

{
    "albums": [
        {
            "id": "1",
            "title": "Kid A",
            "year": 2000,
            "href": "/albums/1",
            "links": {
                "artist": "1"
            }
        },
        {
            "id": "2",
            "title": "Amnesiac",
            "year": 2001,
            "href": "/albums/2",
            "links": {
                "artist": "1"
            }
        },
        {
            "id": "3",
            "title": "Murder Ballads",
            "year": 1996,
            "href": "/albums/3",
            "links": {
                "artist": "2"
            }
        },
        {
            "id": "4",
            "title": "Curtains",
            "year": 2005,
            "href": "/albums/4",
            "links": {
                "artist": "3"
            }
        }
    ],
    "meta": {
        "albums": {
            "page": 1,
            "page_size": 10,
            "count": 4,
            "include": [
                "artists"
            ],
            "page_count": 1,
            "previous_page": null,
            "next_page": null,
            "first_href": '/albums',
            "previous_href": null,
            "next_href": null,
            "last_href": '/albums'
        }
    },
    "links": {
        "albums.songs": {
            "href": "/songs?album_id={albums.id}",
            "type": "songs"
        },
        "albums.artist": {
            "href": "/artists/{albums.artist}",
            "type": "artists"
        },
        "artists.albums": {
            "href": "/albums?artist_id={artists.id}",
            "type": "albums"
        },
        "artists.songs": {
            "href": "/songs?artist_id={artists.id}",
            "type": "songs"
        }
    },
    "linked": {
        "artists": [
            {
                "id": "1",
                "name": "Radiohead",
                "website": "http://radiohead.com/",
                "href": "/artists/1"
            },
            {
                "id": "2",
                "name": "Nick Cave & The Bad Seeds",
                "website": "http://www.nickcave.com/",
                "href": "/artists/2"
            },
            {
                "id": "3",
                "name": "John Frusciante",
                "website": "http://johnfrusciante.com/",
                "href": "/artists/3"
            }
        ]
    }
}

Side-load related Songs

An album :has_many songs, so the side-loaded songs are paged. The meta.songs includes previous_href and next_href which point to the previous and next page of this side-loaded data. These URLs take the form:

Side-load related Artists and Songs

Filtering

Simple filtering based on primary and foreign keys is supported by default:

By primary key:

By foreign key:

Custom filters:

Custom filters can be defined with the can_filter_by option:

class Account
    include RestPack::Serializer
    attributes :id, :application_id, :created_by, :name, :href

    can_filter_by :application_id
end

Side-loading is available when filtering:

Sorting

Sorting attributes can be defined with the can_sort_by option:

class Account
    include RestPack::Serializer
    attributes :id, :application_id, :created_by, :name, :href

    can_sort_by :id, :name
end

Running Tests

bundle rake spec