#320 Jbuilder
- Download:
- source codeProject Files in Zip (102 KB)
- mp4Full Size H.264 Video (20.9 MB)
- m4vSmaller H.264 Video (10.5 MB)
- webmFull Size VP8 Video (11.6 MB)
- ogvFull Size Theora Video (22.7 MB)
If you look at the gemfile for a Rails 3.2 application you’ll see a see a gem mentioned in the comments that wasn’t there in earlier versions called JBuilder.
# To use Jbuilder templates for JSON # gem 'jbuilder'
JBuilder is a template engine for rendering a JSON response. It was created recently by David Heinemeier Hannson but instead of including it in Rails 3.2 he decided to release it as a separate gem. This approach means that it can also be used with earlier versions of Rails. JBuilder provides a nice DSL for generating JSON similar to XML Builder and we’ll show you how to use it in this episode.
Adding JSON Responses to an Application
To demonstrate JBuilder we’ll use a simple blogging application. This app has many articles and we’d like to add a JSON representation for each article that we can fetch by appending .json
to the article’s URL. If we try this now we’ll see an error message as we haven’t yet added this feature.
We could add this without using JBuilder by adding a respond_to
block to the ArticlesController
’s show
action.
def show @article = Article.find(params[:id]) respond_to do |format| format.html format.json { render json: @article } end end
When we reload the page now we’ll see the JSON representation of the article.
Customizing The Response
The JSON returned includes all of the article’s attributes but what if we want to customize it? This is where things can start to get ugly. We can call as_json
on the article and customize what’s returned. Let’s say that we want the id
, name
and content
fields from the article along with its author and the same three fields from the article’s comments.
def show @article = Article.find(params[:id]) respond_to do |format| format.html format.json { render json: @article.as_json(only: [:id, :name, :content], include: [:author, {comments: {only:[:id, :name, :content]}}]) } end end
We can try this by reloading the page again. When we do we see the customized JSON response including the associated Author
and Comment
records.
Using JBuilder
This works, but the code we’ve used isn’t very pretty. We could override as_json
in the model but this wouldn’t be much prettier. This is where JBuilder comes in. To install it we uncomment the relevant line in the gemfile and run bundle
.
# To use Jbuilder templates for JSON gem 'jbuilder'
Back in the controller we can remove the respond_to
call and revert to the default behaviour which is to look for a template for the requested format.
def show @article = Article.find(params[:id]) end
Next we’ll create a JSON template in the /app/views/articles
directory. In it we can use Ruby code to define the JSON output. We have access to a json
object that we can define attributes on like this:
json.id @article.id json.name @article.name
We’ll need to restart the server after we’ve installed the gem but once we have and we reload the page again we’ll see our custom output.
It can be a pain to have to list out each attribute separately like this. Instead we can call extract!
on the JSON object and pass in the object and a list of the methods or attributes we want to call against it.
json.extract! @article, :id, :name, :published_at
There’s an alternative syntax for this as well.
json.(@article, :id, :name, :published_at)
This works only in Ruby 1.9 as it calls call
on the object in the background which will call the extract!
method as before. One nice thing about rendering JSON in a view template like this is that we have access to all of the helper methods. This is especially useful for rendering URLs. If we want to include the edit article URL in the JSON but only if the current user is an admin. We have access to the current_user
method too if the authentication solution we’re using offers this as a helper method.
json.(@article, :id, :name, :published_at) json.edit_url edit_article_url(@article) if current_user.admin?
Our current user is an admin and so the link is included in the JSON.
Nesting
In our application an Article
belongs to an Author
. If we want to include the author’s attributes one way we can do so is like this:
json.(@article, :id, :name, :published_at) json.edit_url edit_article_url(@article) if current_user.admin? json.author @article.author, :id, :name
This will nest the Author
attributes just like we want.
If we need to do something more complex than listing the author’s attributes, let’s say that we want to assign a URL to the author, we can pass a block to the author
call like this:
json.(@article, :id, :name, :published_at) json.edit_url edit_article_url(@article) if current_user.admin? json.author do |json| json.(@article.author, :id, :name) json.url author_url(@article.author) end
Now when we reload the page we’ll see the nested author attributes including the URL.
We can do the same thing for a has_many
association as well. For example an article has many comments and we can add them in directly and list the attributes we want to be displayed.
json.(@article, :id, :name, :published_at) json.edit_url edit_article_url(@article) if current_user.admin? json.author do |json| json.(@article.author, :id, :name) json.url author_url(@article.author) end json.comments @article.comments, :id, :name, :content
Now the comments are included too.
If we need to use the block syntax with this approach it’s a little different because we have an array of comments and we need to iterate through each element of the array. What we do is pass in the json and comment objects into the block which gives us access to it and we can do what we like with it in there.
json.(@article, :id, :name, :published_at) json.edit_url edit_article_url(@article) if current_user.admin? json.author do |json| json.(@article.author, :id, :name) json.url author_url(@article.author) end json.comments @article.comments do |json, comment| json.(comment , :id, :name, :content) end
This will do basically the same thing as the previous code.
Partials
If we find ourselves filling up the comment block with a lot of detail and we need to duplicate this functionality elsewhere we can use a partial. These work in a similar way to view template partials. To use on we need to call partial!
on the json
object and pass in the path to a partial or just an object, in this case a comment.
json.comments @article.comments do |json, comment| json.partial! comment end
This will look for a partial under the app/views/comments
directory called _comment.json.jbuilder
. In this partial we have access to the same json
object and we can do the same thing we’d do in the comment block. We also have access to the comment object here as we passed that in in the partial!
call.
json.(comment, :id, :name, :content)
This will now display the same JSON we had before.
Alternatives
JBuilder isn’t the only gem that does this kind of thing. At the bottom of the project’s README file is a list of alternatives that are worth considering. RABL is the most popular of these and it’s something we might cover in a future episode.