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.jbuilderjson.contentformat_content(@message.content)json.(@message,:created_at,:updated_at)json.authordojson.name@message.creator.name.familiarjson.email_address@message.creator.email_address_with_namejson.urlurl_for(@message.creator,format: :json)endifcurrent_user.admin?json.visitorscalculate_visitors(@message)endjson.comments@message.comments,:content,:created_atjson.attachments@message.attachmentsdo |attachment|
json.filenameattachment.filenamejson.urlurl_for(attachment)endThis 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' <david@heinemeierhansson.com>","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!:authordojson.set!:name,'David'end# => {"author": { "name": "David" }}To merge existing hash or array to current context:
hash={author: {name: "David"}}json.postdojson.title"Merge HOWTO"json.merge!hashend# => "post": { "title": "Merge HOWTO", "author": { "name": "David" } }Top level arrays can be handled directly. Useful for index and other collection actions.
# @comments = @post.commentsjson.array!@commentsdo |comment|
nextifcomment.marked_as_spam_by?(current_user)json.bodycomment.bodyjson.authordojson.first_namecomment.author.first_namejson.last_namecomment.author.last_nameendend# => [ { "body": "great post...", "author": { "first_name": "Joe", "last_name": "Bloe" }} ]You can also extract attributes from array directly.
# @people = People.alljson.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(DavidJamie)json.peoplemy_array# => "people": [ "David", "Jamie" ]You don't always have or need a collection when building an array.
json.peopledojson.child!dojson.id1json.name'David'endjson.child!dojson.id2json.name'Jamie'endend# => { "people": [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ] }Jbuilder objects can be directly nested inside each other. Useful for composing objects.
classPerson# ... Class Definition ... #defto_builderJbuilder.newdo |person|
person.(self,:name,:age)endendendclassCompany# ... Class Definition ... #defto_builderJbuilder.newdo |company|
company.namenamecompany.presidentpresident.to_builderendendendcompany=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 builderjson.contentformat_content(@message.content)json.(@message,:created_at,:updated_at)json.authordojson.name@message.creator.name.familiarjson.email_address@message.creator.email_address_with_namejson.urlurl_for(@message.creator,format: :json)endifcurrent_user.admin?json.visitorscalculate_visitors(@message)endYou 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.commentsIt's also possible to render collections of partials:
json.array!@posts,partial: 'posts/post',as: :post# orjson.partial!'posts/post',collection: @posts,as: :post# orjson.partial!partial: 'posts/post',collection: @posts,as: :post# orjson.comments@post.comments,partial: 'comments/comment',as: :commentThe 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: :commentis quite different from:
# comment attributes are nested under a "comment" propertyjson.commentdojson.partial!"/comments/comment.json.jbuilder",comment: @commentendYou can pass any objects into partial templates with or without :locals option.
json.partial!'sub_template',locals: {user: user}# orjson.partial!'sub_template',user: userYou can explicitly make Jbuilder object return null if you want:
json.extract!@post,:id,:title,:content,:published_atjson.authordoif@post.anonymous?json.null!# or json.nil!elsejson.first_name@post.author_first_namejson.last_name@post.author_last_nameendendTo prevent Jbuilder from including null values in the output, you can use the ignore_nil! method:
json.ignore_nil!json.fooniljson.bar"bar"# => { "bar": "bar" }Fragment caching is supported, it uses Rails.cache and works like caching in
HTML templates:
json.cache!['v1',@person],expires_in: 10.minutesdojson.extract!@person,:name,:ageendYou can also conditionally cache a block by using cache_if! like this:
json.cache_if! !admin?,['v1',@person],expires_in: 10.minutesdojson.extract!@person,:name,:ageendAside 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: trueIf 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.
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: :lowerjson.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_formatcamelize: :lowerBy 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: :lowerjson.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_keystrueTo test the response body of your controller spec, enable render_views in your RSpec context. This configuration renders the views in a controller test.
Jbuilder is the work of many contributors. You're encouraged to submit pull requests, propose features and discuss issues.
See CONTRIBUTING.
Jbuilder is released under the MIT License.