How to have jBuilder return a hash

You may get into a situation where you’d rather get the Hash from jBuilder rather than a string of JSON.

For example, you might normally get the JSON string like this in your controller method:

   data = render_to_string(
        template: "/api/v1/comments/show.json.jbuilder",
        locals: { comment: @comment},
        format: :json
      )

And the show.json.jbuilder

json.partial! "/api/v1/comments/comment.json.jbuilder", comment: @comment

You could instead do this:

    data_hash = JbuilderTemplate.new(view_context) do |json|
      json.partial! "/api/v1/comments/comment.json.jbuilder", comment: @comment
    end.attributes!

    json = data_hash.to_json

Notice for the 2nd one:

  1. We’re rendering a partial, and setting the value of the local variable comment, as is done for the show.json.jbuilder view.
  2. attributes! converts the object into the Hash. Then calling to_json on the Hash will convert it back to json.

The results are not going to be 100% identical as some empiracal results show that Jbuilder render_to_string will round off decimals at 10 digits, whereas calling data_hash.to_json gave us 13 digits of precision.

If you need to combine the result of several Jbuilder partials, then this technique to get a Hash should be quite useful. For example, you can combine the hashes and create a new result.

hash = JbuilderTemplate.new(view_context) do |json|
  json.partial! "/path/to/partial", local_var: local_var_value
end.attributes!

Discussion here:

Might be a better way to do this:

  1. is attributes! (or some other mechanism for getting a Hash) supported?
  2. My use case is that I need to combine the results of several partials dynamically. Is it OK to render to a Hash, then combine the hashes, and call to_json? Or maybe the code should dynamically create a Jbuilder template from several partials? And call render_to_string on that?

CC: @robwise

@robwise, Raphael doesn’t want attributes! as part of the README.md.

How about this way (not tested, just guessing):

 builder = Jbuilder.new do |props|
  props.lessons do
     props.partial! "api/v1/lessons/_lesson_list.json.jbuilder", 
                            lessons: @lessons}
  end
  props.courses do
     props.partial! "api/v1/courses/_course_list.json.jbuilder",
                           courses: @courses
  end
end
json = builder.target!

Target is defined here.