19

I'm porting an application from Merb 1.1 / 1.8.7 to Rails 3 (beta) / 1.9.1 that uses JSON responses containing HTML fragments, e.g., a JSON container specifying an update, on a user record, and the updated user row looks like . In Merb, since whatever a controller method returns is given to the client, one can put together a Hash, assign a rendered partial to one of the keys and return hash.to_json (though that certainly may not be the best way.) In Rails, it seems that to get data back to the client one must use render and render can only be called once, so rendering the hash to json won't work because of the partial render.

From reading around, it seems one could put that data into a JSON .erb view file, with <%= render partial %> in and render that. Is there a Rails-way of solving this problem (return JSON containing one or more HTML fragments) other than that?

In Merb:
controller:

only_provides :json
...
self.status = 204 # or appropriate if not async
return {
    'action' => 'update',
      'type' => 'user',
        'id' => @user.id,
      'html' => partial('user_row', format: :html, user: @user)
}.to_json

In Rails:
controller:

respond_to do |format|
  format.json do
    render template: '/json/message-1', 
      locals: { 
        action: 'update',
        type: 'user',
        id: @user.id,
        partial: 'user_row.html.erb', 
        locals: { user: @user }
      }
  end
end

view: json/message-1.json.erb

{
  "action": <%= raw action.to_json %>,
  "type": <%= raw type.to_json %>,
  "id": <%= raw id.to_json %>,
  "html": <%= raw render(partial: partial, locals: locals).to_json %>
}
ylg
  • 481
  • 1
  • 4
  • 7

3 Answers3

29

The closest to the original from Merb approach I could find in Rails is to use #render_to_string

render json: {
  'action' => 'update',
    'type' => 'user',
      'id' => @user.id,
    'html' => render_to_string(partial: 'user_row.html.erb', locals: { user: @user })
}

This gets around a fair bit of complexity that comes in from adding a layer of json.erb templates into the mix, whether it's Rails Purist approach I couldn't say; possibly something with RJS would typically be used.

Daniel Rikowski
  • 71,375
  • 57
  • 251
  • 329
ylg
  • 481
  • 1
  • 4
  • 7
  • Thanks, I was looking for this "render_to_string" method! – Robin Nov 29 '11 at 22:28
  • 1
    I noticed you can also just add .html as extension, the .erb will be added automatically, useful if you are using slim (.slim) – Francesco Belladonna Mar 20 '13 at 22:50
  • I think descoping formats, in this case having `self.formats += [:html]` in `json/message-1.json.erb` would be more Railsy way to handle this. This method is suggested in other very similar cases: http://stackoverflow.com/questions/5401696/json-erb-template-cannot-find-other-html-partial#5429881 and http://stackoverflow.com/questions/7806533/rails-rendering-a-partial-within-json/15574453#15574453 – Tero Tilus Jan 31 '14 at 09:06
3

There's another question that has more solutions for json.erb files. See json erb template cannot find other html partial

Community
  • 1
  • 1
ajh
  • 121
  • 2
-12
class UsersController < ApplicationController  
  respond_to :json

  def show  
    @user = User.find(params[:id])
    respond_with(@user) do |format|
      if @user.save
        format.json { render :json => @user }
      else
        format.json { render :json => @user.errors, :status => :unprocessable_entity }
      end
    end
  end
end
Joshua Partogi
  • 16,167
  • 14
  • 53
  • 75
  • I'm probably missing the point: the only way I can see in this example to get HTML fragments into the JSON would be to implement #to_json on User and have it render partials, which would presumably run afoul of the double render problem and, even if not, wouldn't allow for different fragments to be used in different contexts, e.g., user_row v. user_detail. Is there something in here I'm not getting? – ylg Mar 17 '10 at 13:35