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.
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 be your Serializers to customize their output:
class AlbumSerializer
include RestPack::Serializer
attributes :id, :title, :year, :artist_id, :extras
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 })
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
:
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums.json
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums/4.json
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:
Collections are paged by default. page
and page_size
parameters are available:
- http://restpack-serializer-sample.herokuapp.com/api/v1/songs.json?page=2
- http://restpack-serializer-sample.herokuapp.com/api/v1/songs.json?page=2&page_size=3
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,
"previous_href": "/api/v1/songs?page_size=3",
"next_href": "/api/v1/songs?page=3&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:
- /artists/1
- /albums/1
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:
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,
"previous_href": null,
"next_href": null
}
},
"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"
}
]
}
}
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:
Simple filtering based on primary and foreign keys is supported by default:
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums.json?id=1
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums.json?ids=1,2,4
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums.json?artist_id=1
- http://restpack-serializer-sample.herokuapp.com/api/v1/albums.json?artist_ids=2,3
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:
bundle
rake spec