Using RABL in Rails JSON Web API
Let's use an event management app as the example.
The app has a simple feature: a user can add some events, then invite other users to attend the event. Its data are represented in 3 models: User, Event, and Event Guest.
Let's say we are going to add a read-only JSON web API to allow clients to browse data records.
Problems
Model is not view
When working on a non-trivial web API, you will soon realize that models often cannot be serialized directly in a web API.
Within the same app, one API may need to render a summary view of the model, while another needs a detail view of the same model. You want to serialize a view or view object, not a model.
RABL (Ruby API Builder Language) gem is designed for this purpose.
Define once, reuse everywhere
Let's say we need to render these user attributes: id, username, email, display_name, but not password.
In RABL, we can define the attribute whitelist in a RABL template.
# tryrabl/app/views/users/base.rabl attributes :id, :username, :email, :display_name
To show an individual user, we can now reuse the template through RABL extends.
# tryrabl/app/views/users/show.rabl extends "users/base" object @user ## JSON output: # { # "user": { # "id": 8, # "username": "blaise", # "email": "matteo@wilkinsonhuel.name", # "display_name": "Ms. Noe Lowe" # } # }
Here's another example to show a list of users.
# tryrabl/app/views/users/index.rabl extends "users/base" collection @users ## JSON output: # [{ # "user": { # "id": 1, # "username": "alanna", # "email": "rubie@hayes.name", # "display_name": "Mrs. Gaylord Hoeger" # } # }, { # "user": { # "id": 2, # "username": "jarrell.robel", # "email": "jarod@eichmann.com", # "display_name": "Oran Lebsack" # } # }]
The template can be reused in nested children as well, through RABL child.
attributes :id, :title, :description, :start, :end, :location child :creator => :creator do extends 'users/base' end ## JSON output: # { # "event": { # "id": 7, # "title": "Et earum sed fuga.", # "description": "Quis sed ..e ad.", # "start": "2011-05-31T08:31:45Z", # "end": "2011-06-01T08:31:45Z", # "location": "Saul Tunnel", # "creator": { # "id": 1, # "username": "alanna", # "email": "rubie@hayes.name", # "display_name": "Mrs. Gaylord Hoeger" # } # } # }
Join table rendered as subclass
I notice a recurring pattern in two recent projects. For instance, in this example, from the client's point of view, Event Guest is basically a User with an additional attribute: RSVP status.
When querying the database, usually we need to query the join table: event_guests.
class GuestsController < ApplicationController def index @guests = EventGuest.where(:event_id => params[:event_id]) end end
But when rendering, the result set needs to be rendered as a list of Users. RABL allows you to do that easily, using its glue feature (a weird name though :).
# tryrabl/app/views/guests/index.rabl collection @event_guests # include the additional attribute attributes :rsvp # add child attributes to parent model glue :user do extends "users/base" end ## JSON output: # [{ # "event_guest": { # "rsvp": "PENDING", # "id": 3, # "username": "myrna_harvey", # "email": "shad.armstrong@littelpouros.name", # "display_name": "Savion Balistreri" # } # }, { # "event_guest": { # "rsvp": "PENDING", # "id": 4, # "username": "adelle.nader", # "email": "brendon.howe@cormiergrady.info", # "display_name": "Edgardo Dickens" # } # }]
The complete Rails example code is available at github.com/teohm/tryrabl.