After talking about Active Model Serializers, let’s see an alternative.
The goals are the same as Active Model Serializers :
- Deliver JSON through your API
- Separate the presentation concern from the model
- Control what goes in your JSON
Jbuilder represents a totally different approach. Where Active Model Serializers use simple Ruby objects, Jbuilder offers a simple DSL that let you write JSON ‘views’.
Let’s see how it works by building a Rails API !
Source Code
Setup the application
Since I am building an API here, I will use the rails-api gem.
rails-api new jbuilder_tuto
If you don’t have rails-api, you can install it with gem install rails-api. You can also use a standard Rails app if you prefer.
Create the models
Let’s get started. We are going to create our models :
rails g model Artist name:string label:string
rails g model Album name:string release_date:date artist_id:integer
rails g model Song name:string release_date:date lyrics:text album_id:integer
Migrate all that :
rake db:migrate
We have to add relations between our models :
# app/models/artist.rb
class Artist < ActiveRecord::Base
has_many :albums
end
# app/models/album.rb
class Album < ActiveRecord::Base
belongs_to :artist
has_many :songs
end
# app/models/song.rb
class Song < ActiveRecord::Base
belongs_to :album
end
JBuilder
Now, we’re going to add JBuilder and start making some cool JSON ! Note that if you are using rails instead of rails-api, jbuilder will already be in your Gemfile !
# Gemfile.rb
# ...
gem 'jbuilder'
# ...
‘bundle install’ and restart your application.
The Data
Here is a set of data you can easily put in seed.rb and import with ‘rake db:seed’(It’s the same than for Active Model Serializers) :
muse = Artist.create( name: 'Muse', label: 'Warner Bros.')
black = muse.albums.create( name: 'Black Holes and Revelations', release_date: '03/07/2006' )
resistance = muse.albums.create( name: 'The Resistance', release_date: '11/09/2009' )
["Take a Bow", "Starlight", "Supermassive Black Hole", "Map of the Problematique", "Soldier's Poem", "Invincible", "Assassin", "Exo-Politics", "City of Delusion", "Hoodoo", "Knights of Cydonia"].each do |song|
resistance.songs.create( name: song, release_date: resistance.release_date, lyrics: '...' )
end
red = Artist.create( name: 'Red Hot Chili Peppers', label: 'EMI')
californication = red.albums.create( name: 'Californication', release_date: '08/06/1999' )
["Around the World" , "Parallel Universe", "Scar Tissue", "Otherside", "Get on Top" , "Californication", "Easily" , "Porcelain", "Emit Remmus", "I Like Dirt", "This Velvet Glove", "Savior" , "Purple Stain" , "Right on Time", "Road Trippin"].each do |song|
californication.songs.create( name: song, release_date: californication.release_date, lyrics: '...' )
end
Now, we need to create a controller that will be responsible for rendering the views.
The Artist Controller
If you are using rails-api, you will want to add one module to your ApplicationController :
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::ImplicitRender
end
You don’t need that if you are using a normal Rails app.
Now, let’s add the controller :
# app/controllers/artists_controller.rb
class ArtistsController < ApplicationController
def index
@artists = Artist.all
end
end
Update the routes :
Rails.application.routes.draw do
resources :artists
end
The last missing part is the actual view. We’re going to create it in’app/views/artists/‘ and it will contain the rendering logic of our JSON.
Here’s the content :
# app/views/artists/index.json.jbuilder
json.artists @artists
Now go to http://localhost:3000/artists.json and you should see the following :
Great ! But you probably don’t want to show the timestamps like this. What if I want a description containing the name of the band and the label producing them ? Can we do that ? Short answer, yes !
First, if we want just the id, name and label we can do this :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.id artist.id
json.name artist.name
json.label artist.label
end
or shorter :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
end
or if you don’t like the previous syntax :
# app/views/artists/index.json.jbuilder
json.array! @artists do |artist|
json.extract! artist, :id, :name, :label
end
or if you like to type a lot :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.set! :id, artist.id
json.set! :name, artist.name
json.set! :label, artist.label
end
You can also create methods in your models and use them in the jbuilder template :
# app/models/artist.rb
class Artist < ActiveRecord::Base
has_many :albums
def name_with_label
"#{name} produced by #{label}"
end
end
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :name_with_label)
end
Result :
We can also put it in a helper. Jbuilder templates behave like regular html templates so you can use any helper method.
If you use rails-api and you want to use helpers, you need to add this to your ApplicationController and create the app/helpers folder:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::ImplicitRender
include ActionController::Helpers
end
Now create ‘app/helpers/artists_helper.rb’ if you don’t have it :
# app/helpers/artists_helper.rb
module ArtistsHelper
def name_with_label(artist)
"#{artist.name} produced by #{artist.label}"
end
end
With this, you can just do :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
json.name_with_label name_with_label(artist)
end
Another cool thing is that Jbuilder handles conditions.
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
if artist.id == 1
json.name_with_label name_with_label(artist)
end
end
And loops ! (even useless ones…)
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
5.times do |i|
json.set! "count_#{i}", i
end
end
Ok, enough fun. You can do pretty much everything you would do in a regular html views inside Jbuilder templates. Let’s see how to handle embedded models.
Well, it’s actually super easy :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
json.albums artist.albums do |album|
json.(album, :id, :name)
end
end
Will give you :
The problem is that it’s not reusable… Good thing, Jbuilder can handle partials !
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
json.albums artist.albums, partial: 'albums/album', as: :album
end
# app/views/albums/_album.json.jbuilder
json.(album, :id, :name)
Et voila :
Let’s add the songs inside the albums :
# app/views/albums/_album.json.jbuilder
json.(album, :id, :name)
json.songs album.songs, partial: 'songs/song', as: :song
# app/views/songs/_song.json.jbuilder
json.(song, :id, :name, :lyrics)
As you can see Jbuilder is really easy to use and let you build completly customizable JSONs !
Here are a few more tips.
array!
You can use ‘array!’ to create, well, an array :
# app/views/artists/index.json.jbuilder
json.array! @artists do |artist|
json.(artist, :id, :name, :label)
end
This will give you an array of artists, without ‘artist: { … }’.
null!
Want a null value ? No problem !
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
if artist.id == 0
json.(artist, :id, :name, :label)
json.albums artist.albums, partial: 'albums/album', as: :album
else
json.null!
end
end
Will give a beautiful ‘[null, null]’ array. Please, please, write smarter conditions than me…
cache!
Required for rails-api :
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::ImplicitRender
include ActionController::Helpers
include ActionController::Caching
end
Fragment caching works just like in HTML Templates :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.cache! ['v1', artist], expires_in: 1.minutes do
json.(artist, :id, :name, :label)
json.albums artist.albums, partial: 'albums/album', as: :album
end
end
What’s that ? Why use caching ? Well, to give you an example, on the very simple/small json of this tutorial, it divided the request duration by 4 (12ms to 4ms). Now, imagine with a huge JSON !
cache_if!
With ‘cache_if!’, you can cache only if a specific condition is met :
# app/views/artists/index.json.jbuilder
json.artists @artists do |artist|
json.cache_if! artist.id == 1, ['v1', artist], expires_in: 1.minutes do
json.(artist, :id, :name, :label)
json.albums artist.albums, partial: 'albums/album', as: :album
end
end
key_format!
Don’t like snake_case ? You can camelize the keys if you prefer.
# app/views/artists/index.json.jbuilder
json.key_format! camelize: :lower
json.this_is_madness 'This. is. Sparta!'
json.artists @artists do |artist|
json.(artist, :id, :name, :label)
json.albums artist.albums, partial: 'albums/album', as: :album
end
The End
Well, I think that’s about it. Lots of good things here. Now, you know how to use yet another JSON builder ! :D
Want faster JSON ?
Checkout Oj (Optimized JSON) by Peter Ohler.