25

I'm using JBuilder as to return some JSON. I have a index.json.jbuilder that generates the data, and I need to render it to a string. However, I'm not sure how to do this, since: @my_object.to_json and @my_object.as_json don't seem to go through JBuilder.

How could I render the JBuilder view as a string?

Geo
  • 93,257
  • 117
  • 344
  • 520
  • Are you trying to return JSON from a controller as a JSON response or literally build a JSON string in isolation? – Winfield Apr 18 '12 at 20:15

8 Answers8

45

I am rendering a collection of users as a json string in the controller like so:

#controllers/users_controller.rb
def index
  @users = User.all
  @users_json = render_to_string( template: 'users.json.jbuilder', locals: { users: @users})
end

#views/users/users.json.jbuilder
json.array!(users) do |json, user|
  json.(user, :id, :name)
end
Aaron Renoir
  • 4,283
  • 1
  • 39
  • 61
  • Hi Aaron, I tried implementing the above technique, but when I go to the page, I get a missing template error: "Missing template /work.json with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :arb, :jbuilder, :coffee]}." I'm a little new to rails, and not quite sure what's going on. Any help would be very much appreciated. Thanks in advance! – jordancooperman Mar 27 '13 at 02:47
  • I am guessing you may need to add "respond_to :json" in your controller. – Aaron Renoir Mar 27 '13 at 03:00
  • I'm still getting the same error with "respond_to :json" in my controller – jordancooperman Mar 27 '13 at 03:20
  • @arron Not sure if this is the best way, but I finally got it working like this: "@work_json = render_to_string "work", :layout => false", and renaming my jbuilder file to "work.html.jbuilder". Your answer definitely put me on the right track though. +1 thanks! – jordancooperman Mar 31 '13 at 20:35
  • 3
    the answer doesn't work for me. render_to_string changes the content type of the response to json (index should render html) – vrepsys Feb 12 '14 at 08:54
  • This doesn't work as written here -- same error as jordancooperman. – maletor Mar 10 '14 at 21:43
  • calling the index method is going to render a string and assign it to the @users_json instance variable, from there you do what you please. If you want to output something you need to add a respond to block or create a view for the requested content type. – Aaron Renoir Mar 11 '14 at 00:10
  • maletor if you are having a problem rendering the template you may need to specify the format. render_to_string :template => "template", :formats => [:json] rails usually deduces the format from the template name "users.json.jbuilder" format should be set as json. – Aaron Renoir Mar 11 '14 at 00:18
  • If you want the view to continue rendering HTML after producing the JSON (for example to include the JSON in the HTML page), you need to tell Rails to switch back to HTML format. You can add a line at the end of the action like this: `render :index, formats: [:html]` – jwadsack Sep 25 '15 at 15:26
9

If the view users.json.jbuilder is at the default path relative to the controller and it cannot find the template, it may be due to a format discrepancy, as it may be trying to look for the html format file. There are two ways to fix this:

  1. Have the client GET /users/index.json

    or

  2. Specify the formats option when calling render_to_string (also applies to render):


#controllers/users_controller.rb
def index
  @users = User.all
  @users_json = render_to_string( formats: 'json' ) # Yes formats is plural
end

This has been verified in Rails 4.1.

Matt
  • 20,108
  • 1
  • 57
  • 70
5

If you're doing this in the controller, a much simpler option is to try to the move the code into the view being rendered by the controller.

I described this here: https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/docs/jbuilder.md

basically you can call render in the view, and you're done. Like this:

<%= react_component('App', render(template: "/comments/index.json.jbuilder"),
    generator_function: true, prerender: true) %>

Here's the notes on what happens if you want to pass the data from the controller to the view:

class PagesController < ApplicationController
  def index
    @comments = Comment.all

    # NOTE: The below notes apply if you want to set the value of the props in the controller, as
    # compared to he view. However, it's more convenient to use Jbuilder from the view. See
    # app/views/pages/index.html.erb:20
    #
    #  <%= react_component('App', render(template: "/comments/index.json.jbuilder"),
    #     generator_function: true, prerender: true) %>
    #
    #
    # NOTE: this could be an alternate syntax if you wanted to pass comments as a variable to a partial
    # @comments_json_sting = render_to_string(partial: "/comments/comments.json.jbuilder",
    #                                         locals: { comments: Comment.all }, format: :json)
    # NOTE: @comments is used by the render_to_string call
    # @comments_json_string = render_to_string("/comments/index.json.jbuilder")
    # NOTE: It's CRITICAL to call respond_to after calling render_to_string, or else Rails will
    # not render the HTML version of the index page properly. (not a problem if you do this in the view)
    # respond_to do |format|
    #   format.html
    # end
  end
end
justingordon
  • 12,553
  • 12
  • 72
  • 116
3

You can also do it like this, which leaves your controller a bit cleaner.

# controller
def new
  @data = Data.all
end


# view
<% content_for :head do %>
  <script type="text/javascript">
    var mydata = <%= raw render :partial => 'path/to/partial', :locals => {data: @data} %>;
  </script>
<% end %>


# path/to/_partial.html.jbuilder
json.array!(@data) do |d|
  json.extract! field1, :field2, :field3, :field4
  json.url data_url(d, format: :json)
end


# layouts/application.html
<!DOCTYPE html>
<html>
<head>
  <%= yield :head %>
</head>
<body>
...
</body>
</html>
asgeo1
  • 9,028
  • 6
  • 63
  • 85
  • I'd prefer this approach, also, you can make it format agnostic by dropping format part from extensions, i.e. `_partial.jbuilder` can be used from inside of both json and html views. – wik Jan 06 '14 at 21:53
  • This approach is nice, except it doesn't work within `content_tag`'s data attributes. What gives? – maletor Mar 10 '14 at 21:28
2

Looking at the source code, it looks like you can do:

json_string = Jbuilder.encode do |json|
  json.partial! 'path/to/index', @my_object
end
Winfield
  • 18,985
  • 3
  • 52
  • 65
  • I tried for about 20 minutes in a shell to do it, without much luck. If it's not too much to ask, could you maybe post a working example of this? – Geo Apr 19 '12 at 11:12
  • I pulled this code snippet from the Jbuilder examples in the docs: https://github.com/rails/jbuilder It looks like embedded templates may be broken under Ruby 1.8 and only work on Ruby 1.9 and above. – Winfield Apr 19 '12 at 18:26
  • json.partial! 'path/to/index', my_object: @my_object This is right syntax – maksfromspb Aug 06 '15 at 14:59
  • json = JbuilderTemplate.new(view_context) do |json| json.partial! 'filename' end.attributes! note that you need "_filename" because it calls partial renderer. – kuboon Sep 04 '16 at 09:34
2

From console:

view = ApplicationController.view_context_class.new("#{Rails.root}/app/views")
JbuilderTemplate.encode(view){|json| json.partial!('path/to/index', @my_object) }

via https://github.com/rails/jbuilder/issues/84#issuecomment-38109709

James EJ
  • 1,955
  • 1
  • 18
  • 16
1

in controller you can do like this

def index
  json = JbuilderTemplate.new(view_context) do |json|
    json.partial! 'index'
  end.attributes!
  do_something(json)
  render json: json
end

note that you need "_index.json.jbuilder" because it calls partial renderer

kuboon
  • 9,557
  • 3
  • 42
  • 32
0

Following justingordon's tip.

If you are using a React component, you can do the following.

In your controller:

@users = User.all

In your view:

<%= react_component("YourComponentName",
                    props: render('your_template.json.jbuilder')) %>

This was tested on Rails 5.1.

kairon
  • 31
  • 1
  • 1