127

I'm trying to generate a JSON response that includes some HTML. Thus, I have /app/views/foo/bar.json.erb:

{
  someKey: 'some value',
  someHTML: "<%= h render(:partial => '/foo/baz') -%>"
}

I want it to render /app/views/foo/_baz.html.erb, but it will only render /app/views/foo/_baz.json.erb. Passing :format => 'html' doesn't help.

James A. Rosen
  • 64,193
  • 61
  • 179
  • 261
  • The question title is much more general than James's particular problem. If you are looking for other solutions, take a look here: http://stackoverflow.com/questions/7616097/how-to-render-new-js-coffee-erb-in-app-views – miguelsan Aug 08 '13 at 13:14

11 Answers11

106

Beginning with Rails 3.2.3, when calling render :partial (only works outside of the respond_to block).

render formats: [ :html ]

instead of

render format: 'html'
Itay Grudev
  • 7,055
  • 4
  • 54
  • 86
Tim Haines
  • 1,496
  • 3
  • 14
  • 16
  • I guess there may be different "best" answers depending on context, so it's hard to meaningfully upvote on this question, but this filled my need of easily rendering an HTML template from within an RJS one of the same name after hoping that a `:format` option would work. Thanks! – ches Mar 20 '13 at 16:18
  • 1
    This also works for Rails 5.0.2. However, note that you may also need to specify the `:content_type` option in some cases — e.g. rendering a HTML template from inside a `format.pdf` block can only be done with `render "template", formats: [:html], content_type: "text/html"`. I need this because I allow direct download of samples of [my books](https://efficientrailsdevops.com) only for members of my email list — regular visitors get a signup form instead. – Michael Trojanek Mar 04 '17 at 10:35
64

What's wrong with

render :partial => '/foo/baz.html.erb'

? I just tried this to render an HTML ERB partial from inside an Atom builder template and it worked fine. No messing around with global variables required (yeah, I know they have "@" in front of them, but that's what they are).

Your with_format &block approach is cool though, and has the advantage that you only specify the format, whereas the simple approach specifies the template engine (ERB/builder/etc) as well.

Community
  • 1
  • 1
Sam Stokes
  • 14,617
  • 7
  • 36
  • 33
  • This is really the most straightforward approach, and works seamlessly for me. – Derek P. Jun 23 '09 at 23:04
  • 26
    The only downside to this is that if your partial renders other partials it'll fail unless you go in and change all your render partial calls to include the ".html.erb" on their name. – chrisrbailey Jul 25 '09 at 02:40
  • +1 for sure, but having to specify the template engine, combined with the concern chrisbailey raises about nested partials makes me wary. – James A. Rosen Jan 13 '10 at 22:29
  • 4
    you don't have to specify the templating engine for this to work. (At least as of rails 3). The following works just fine: render(:partial => "baz.html") – Tim Harper May 07 '10 at 17:53
  • 1
    Doesn't work if you have different partials for different locales (e.g. `baz.en.html.erb`, `baz.fr.html.erb`) and you want `render :partial`s logic for choosing the right one (with fallbacks, etc). – John Feb 01 '12 at 23:55
  • 1
    As of rails 3.2.3, I'm seeing the following warning when using this solution: `DEPRECATION WARNING: Passing a template handler in the template name is deprecated. You can simply remove the handler name or pass render :handlers => [:erb] instead. ` – YWCA Hello May 17 '12 at 00:20
  • 3
    One downside of this approach is that localizations will look for `foo.baz.html.[your_string]` instead of `foo.baz.[your_string]`. zgchurc's answer is a better solution. – mbillard Jul 23 '12 at 13:44
  • I think this is getting deprecated in rails 4 – Sam Saffron Aug 09 '12 at 05:18
39

Rails 4 will allow you to pass a formats parameter. So you can do

render(:partial => 'form', :formats => [:html])} 

Note you can do something similar in Rails 3 but it wouldn't pass that format to any sub partials (if form calls other partials).

You can have the Rails 4 ability in Rails 3 by creating config/initializers/renderer.rb:

class ActionView::PartialRenderer
  private
  def setup_with_formats(context, options, block)
    formats = Array(options[:formats])
    @lookup_context.formats = formats | @lookup_context.formats
    setup_without_formats(context, options, block)
  end

  alias_method_chain :setup, :formats
end

See http://railsguides.net/2012/08/29/rails3-does-not-render-partial-for-specific-format/

DrewB
  • 3,231
  • 24
  • 22
  • odd, I wonder why it's not working for me in a Rails 3.2.19 app, wonder what makes the difference in my app. Can't get ActionView render :partial to respect the :formats arg, seems to make no difference at all, even with this patch. – jrochkind Oct 07 '14 at 05:14
32

For Rails 3, the with_format block works, but it's a little different:

  def with_format(format, &block)
    old_formats = formats
    self.formats = [format]
    block.call
    self.formats = old_formats
    nil
  end
zgchurch
  • 2,302
  • 1
  • 14
  • 7
30

Building on roninek's response, I've found the best solution to be the following:

in /app/helpers/application.rb:

def with_format(format, &block)
  old_format = @template_format
  @template_format = format
  result = block.call
  @template_format = old_format
  return result
end

In /app/views/foo/bar.json:

<% with_format('html') do %>
  <%= h render(:partial => '/foo/baz') %>
<% end %>

An alternate solution would be to redefine render to accept a :format parameter.

I couldn't get render :file to work with locals and without some path wonkiness.

Community
  • 1
  • 1
James A. Rosen
  • 64,193
  • 61
  • 179
  • 261
25

In Rails 3, the View has a formats array, which means you can set it to look for [:mobile, :html]. Setting that will default to looking for :mobile templates, but fall back to :html templates. The effects of setting this will cascade down into inner partials.

The best, but still flawed way, that I could find to set this was to put this line at the top of each full mobile template (but not partials).

<% self.formats = [:mobile, :html] %>

The flaw is that you have to add that line to multiple templates. If anyone knows a way to set this once, from application_controller.rb, I'd love to know it. Unfortunately, it doesn't work to add that line to your mobile layout, because the templates are rendered before the layout.

  • Thank you for explaining this. I ended up using it elsewhere: https://stackoverflow.com/questions/47459724/getting-format-in-mailer-view-in-rails – Joshua Pinter Nov 23 '17 at 19:00
16

Just elaborating on what zgchurch wrote:

  • taking exceptions into account
  • returning the result of the called block

Thought it might be useful.

def with_format(format, &block)
  old_formats = formats
  begin
    self.formats = [format]
    return block.call
  ensure
    self.formats = old_formats
  end
end
viphe
  • 689
  • 8
  • 16
10

You have two options:

1) use render :file

render :file => "foo/_baz.json.erb"

2) change template format to html by setting @template_format variable

<% @template_format = "html" %>
<%= h render(:partial => '/foo/baz') %>
Ken Ratanachai S.
  • 3,307
  • 34
  • 43
roninek
  • 126
  • 2
  • 1
    <% @template_format = "html" %> worked for me, as my partial loaded other partials I did not have to update the other renders down the chain. – pagetribe Mar 14 '11 at 00:44
7

I had a file named 'api/item.rabl' and I wanted to render it from an HTML view so I had to use:

render file: 'api/item', formats: [:json]

(file because the file have no underscore in the name, formats and not format (and passes and array))

Dorian
  • 22,759
  • 8
  • 120
  • 116
  • Good answer. Rails 6.1 deprecated having a `.` in the name of the partial so you can do this instead: `render partial: 'api/item', formats: [:json]`. – Brendon Muir Apr 15 '21 at 09:42
2

It seems that passing a formats option will render it properly in newer Rails version, at least 3.2:

{
  someKey: 'some value',
  someHTML: "<%= h render('baz', formats: :html) -%>"
}
Mario Uher
  • 12,249
  • 4
  • 42
  • 68
1

I came across this thread when I was trying to render an XML partial in another xml.builder view file. Following is a nice way to do it

xml.items :type => "array" do
    @items.each do |item|
        xml << render(:partial => 'shared/partial.xml.builder', :locals => { :item => item })
    end
end

And yeah... Full file name works here as well...

bragboy
  • 34,892
  • 30
  • 114
  • 171
Garfield
  • 1,247
  • 4
  • 15
  • 33
  • This suffers from the same inner-partials-problem that @chrisrbailey mentioned on another answer: if the partial you call with a full filename itself uses partials (without specifying the full file name for each), it will fail. – James A. Rosen Jun 23 '10 at 12:28