Jbuilder
Jbuilder gives you a simple DSL for declaring JSON structures that beats
manipulating giant hash structures. This is particularly helpful when the
generation process is fraught with conditionals and loops. Here’s a simple
example:
# app/views/messages/show.json.jbuilder json.content format_content(@message.content) json.(@message, :created_at, :updated_at) json.author do json.name @message.creator.name.familiar json.email_address @message.creator.email_address_with_name json.url url_for(@message.creator, format: :json) end if current_user.admin? json.visitors calculate_visitors(@message) end json.comments @message.comments, :content, :created_at json.attachments @message.attachments do |attachment| json.filename attachment.filename json.url url_for(attachment) end
This will build the following structure:
{ "content": "<p>This is <i>serious</i> monkey business</p>", "created_at": "2011-10-29T20:45:28-05:00", "updated_at": "2011-10-29T20:45:28-05:00", "author": { "name": "David H.", "email_address": "'David Heinemeier Hansson' ", "url": "http://example.com/users/1-david.json" }, "visitors": 15, "comments": [ { "content": "Hello everyone!", "created_at": "2011-10-29T20:45:28-05:00" }, { "content": "To you my good sir!", "created_at": "2011-10-29T20:47:28-05:00" } ], "attachments": [ { "filename": "forecast.xls", "url": "http://example.com/downloads/forecast.xls" }, { "filename": "presentation.pdf", "url": "http://example.com/downloads/presentation.pdf" } ] }
To define attribute and structure names dynamically, use the set!
method:
json.set! :author do json.set! :name, 'David' end # => {"author": { "name": "David" }}
To merge existing hash or array to current context:
hash = { author: { name: "David" } } json.post do json.title "Merge HOWTO" json.merge! hash end # => "post": { "title": "Merge HOWTO", "author": { "name": "David" } }
Top level arrays can be handled directly. Useful for index and other collection actions.
# @comments = @post.comments json.array! @comments do |comment| next if comment.marked_as_spam_by?(current_user) json.body comment.body json.author do json.first_name comment.author.first_name json.last_name comment.author.last_name end end # => [ { "body": "great post...", "author": { "first_name": "Joe", "last_name": "Bloe" }} ]
You can also extract attributes from array directly.
# @people = People.all json.array! @people, :id, :name # => [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ]
To make a plain array without keys, construct and pass in a standard Ruby array.
my_array = %w(David Jamie) json.people my_array # => "people": [ "David", "Jamie" ]
You don’t always have or need a collection when building an array.
json.people do json.child! do json.id 1 json.name 'David' end json.child! do json.id 2 json.name 'Jamie' end end # => { "people": [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ] }
Jbuilder objects can be directly nested inside each other. Useful for composing objects.
class Person # ... Class Definition ... # def to_builder Jbuilder.new do |person| person.(self, :name, :age) end end end class Company # ... Class Definition ... # def to_builder Jbuilder.new do |company| company.name name company.president president.to_builder end end end company = Company.new('Doodle Corp', Person.new('John Stobs', 58)) company.to_builder.target! # => {"name":"Doodle Corp","president":{"name":"John Stobs","age":58}}
You can either use Jbuilder stand-alone or directly as an ActionView template
language. When required in Rails, you can create views à la show.json.jbuilder
(the json is already yielded):
# Any helpers available to views are available to the builder json.content format_content(@message.content) json.(@message, :created_at, :updated_at) json.author do json.name @message.creator.name.familiar json.email_address @message.creator.email_address_with_name json.url url_for(@message.creator, format: :json) end if current_user.admin? json.visitors calculate_visitors(@message) end
You can use partials as well. The following will render the file
views/comments/_comments.json.jbuilder
, and set a local variable
comments
with all this message’s comments, which you can use inside
the partial.
json.partial! 'comments/comments', comments: @message.comments
It’s also possible to render collections of partials:
json.array! @posts, partial: 'posts/post', as: :post # or json.partial! 'posts/post', collection: @posts, as: :post # or json.partial! partial: 'posts/post', collection: @posts, as: :post # or json.comments @post.comments, partial: 'comments/comment', as: :comment
The as: :some_symbol
is used with partials. It will take care of mapping the passed in object to a variable for the
partial. If the value is a collection either implicitly or explicitly by using the collection:
option, then each
value of the collection is passed to the partial as the variable some_symbol
. If the value is a singular object,
then the object is passed to the partial as the variable some_symbol
.
Be sure not to confuse the as:
option to mean nesting of the partial. For example:
# Use the default `views/comments/_comment.json.jbuilder`, putting @comment as the comment local variable. # Note, `comment` attributes are "inlined". json.partial! @comment, as: :comment
is quite different from:
# comment attributes are nested under a "comment" property json.comment do json.partial! "/comments/comment.json.jbuilder", comment: @comment end
You can pass any objects into partial templates with or without :locals
option.
json.partial! 'sub_template', locals: { user: user } # or json.partial! 'sub_template', user: user
You can explicitly make Jbuilder object return null if you want:
json.extract! @post, :id, :title, :content, :published_at json.author do if @post.anonymous? json.null! # or json.nil! else json.first_name @post.author_first_name json.last_name @post.author_last_name end end
To prevent Jbuilder from including null values in the output, you can use the ignore_nil!
method:
json.ignore_nil! json.foo nil json.bar "bar" # => { "bar": "bar" }
Caching
Fragment caching is supported, it uses Rails.cache
and works like caching in
HTML templates:
json.cache! ['v1', @person], expires_in: 10.minutes do json.extract! @person, :name, :age end
You can also conditionally cache a block by using cache_if!
like this:
json.cache_if! !admin?, ['v1', @person], expires_in: 10.minutes do json.extract! @person, :name, :age end
Aside from that, the :cached
options on collection rendering is available on Rails >= 6.0. This will cache the
rendered results effectively using the multi fetch feature.
json.array! @posts, partial: "posts/post", as: :post, cached: true # or: json.comments @post.comments, partial: "comments/comment", as: :comment, cached: true
If your collection cache depends on multiple sources (try to avoid this to keep things simple), you can name all these dependencies as part of a block that returns an array:
json.array! @posts, partial: "posts/post", as: :post, cached: -> post { [post, current_user] }
This will include both records as part of the cache key and updating either of them will expire the cache.
Formatting Keys
Keys can be auto formatted using key_format!
, this can be used to convert
keynames from the standard ruby_format to camelCase:
json.key_format! camelize: :lower json.first_name 'David' # => { "firstName": "David" }
You can set this globally with the class method key_format
(from inside your
environment.rb for example):
Jbuilder.key_format camelize: :lower
By default, key format is not applied to keys of hashes that are
passed to methods like set!
, array!
or merge!
. You can opt into
deeply transforming these as well:
json.key_format! camelize: :lower json.deep_format_keys! json.settings([{some_value: "abc"}]) # => { "settings": [{ "someValue": "abc" }]}
You can set this globally with the class method deep_format_keys
(from inside your
environment.rb for example):
Jbuilder.deep_format_keys true
Testing JBuilder Response body with RSpec
To test the response body of your controller spec, enable render_views
in your RSpec context. This configuration renders the views in a controller test.
Contributing to Jbuilder
Jbuilder is the work of many contributors. You’re encouraged to submit pull requests, propose
features and discuss issues.
See CONTRIBUTING.
License
Jbuilder is released under the MIT License.