103

When I render a partial which does not exists, I get an Exception. I'd like to check if a partial exists before rendering it and in case it doesn't exist, I'll render something else. I did the following code in my .erb file, but I think there should be a better way to do this:

    <% begin %>
      <%= render :partial => "#{dynamic_partial}" %>
    <% rescue ActionView::MissingTemplate %>
      Can't show this data!
    <% end %>
Daniel Cukier
  • 11,502
  • 15
  • 68
  • 123

8 Answers8

106

Currently, I'm using the following in my Rails 3/3.1 projects:

lookup_context.find_all('posts/_form').any?

The advantage over other solutions I've seen is that this will look in all view paths instead of just your rails root. This is important to me as I have a lot of rails engines.

This also works in Rails 4.

doppelgreener
  • 4,809
  • 10
  • 46
  • 63
Rein
  • 1,096
  • 1
  • 8
  • 3
  • 9
    lookup_context.exists?('posts/find') didn't work for me. Instead I used lookup_context.exists?(name, prefix, partial) or lookup_content.exists?('find', 'posts', true) in this example. – Jenn Jun 12 '12 at 17:30
  • 2
    This is the current (rails >= 3.2) way to check for templates (source [apidock](http://apidock.com/rails/ActionView/LookupContext/ViewPaths/template_exists%3F)) – maček Dec 11 '12 at 20:47
  • 2
    If the partial is in the same folder as the current view template, you can use `lookup_context.exists?("find", lookup_context.prefixes, true)`. This way, you don't need to hard-code the view directory into the call. Note, this is for partials. For non-partials, omit the last arg (or use false instead of true) – Nathan Wallace Jun 12 '17 at 14:45
74

I was struggling with this too. This is the method I ended up using:

<%= render :partial => "#{dynamic_partial}" rescue nil %>

Basically, if the partial doesn't exist, do nothing. Did you want to print something if the partial is missing, though?

Edit 1: Oh, I fail at reading comprehension. You did say that you wanted to render something else. In that case, how about this?

<%= render :partial => "#{dynamic_partial}" rescue render :partial => 'partial_that_actually_exists' %>

or

<%= render :partial => "#{dynamic_partial}" rescue "Can't show this data!" %>

Edit 2:

Alternative: Checking for existence of the partial file:

<%= render :partial => "#{dynamic_partial}" if File.exists?(Rails.root.join("app", "views", params[:controller], "_#{dynamic_partial}.html.erb")) %>
Jeff
  • 21,744
  • 6
  • 51
  • 55
  • 6
    My question is that I don't want to use Exceptions to do the flow control, which is a anti-pattern: http://stackoverflow.com/questions/1546514/java-exceptions-as-control-flow – Daniel Cukier Aug 26 '10 at 13:46
  • 6
    An exception is a type of flow control used to handle things that happen beyond a program's normal operation. If the dynamic partial is supposed to be there but something goes wrong and it ends up not being there, then that is a reasonable use for an exception (IMO, of course - proper use of exceptions is a holy war itself). I would say your alternative is to check the filesystem for whether or not the actual file exists. I'll update my answer with that code. – Jeff Aug 26 '10 at 20:08
  • 3
    I like the solution, nevertheless it swallows any kind of exception thrown in the partial. IMHO this makes it harder to track down errors. – Matt Aug 26 '11 at 20:25
  • 5
    If you have a different type of exception, the `rescue nil` and `... rescue ...` methods will hide it. That leads to bugs that are hard to debug. – nicholaides Dec 23 '11 at 17:04
  • 8
    I really dislike this solution. rescuing is expensive, and File.exists? assumes that the partial can only be in one location. @Rein's solution using the lookup_context is the way to go I believe. – Bert Goethals Feb 14 '12 at 09:32
  • Yes, LookupContext should be used in Rails 3. Keep in mind this question is from the era of 2.3.x. – Jeff Feb 14 '12 at 16:30
55

From inside a view, template_exists? works, but the calling convention doesn't work with the single partial name string, instead it takes template_exists?(name, prefix, partial)

To check for partial on path: app/views/posts/_form.html.slim

Use:

lookup_context.template_exists?("form", "posts", true)
Brad Werth
  • 17,411
  • 10
  • 63
  • 88
Luke Imhoff
  • 802
  • 10
  • 11
  • On Rails 3.0.10 I found that if I have an alternate extension, like app/views/posts/_foo.txt.erb, I needed to add that to the argument as: template_exists?("foo.txt", "posts", true) – Gabe Martin-Dempesy Oct 27 '11 at 22:25
  • This is deprecated in rails 3.2 – maček Dec 11 '12 at 20:45
  • It does not appear to be delegated in Rails 3.2.x, however, the second argument is an array of prefixes. Further, it exists on the current controller. – Brendan Dec 25 '12 at 20:44
  • 3
    You can use lookup_context.prefixes as the second argument: lookup_context.template_exists?("form", lookup_context.prefixes, true) – lion.vollnhals Jun 11 '14 at 17:37
  • This is the better answer in terms of accessing this information from the view layer. – Brendon Muir Sep 14 '16 at 02:06
31

In Rails 3.2.13, if you're in a controller, you can use this :

template_exists?("#{dynamic_partial}", _prefixes, true)

template_exists? is delegated to lookupcontext, as you can see in AbstractController::ViewPaths

_prefixes gives the context of the controller's inheritance chain.

true because you're looking for a partial (you can omit this argument if you want a regular template).

http://api.rubyonrails.org/classes/ActionView/LookupContext/ViewPaths.html#method-i-template_exists-3F

Flackou
  • 3,631
  • 4
  • 27
  • 24
  • Upvoted. More up-to-date and better explanation of parameters. – jacobsimeon Oct 15 '13 at 16:21
  • 4
    From a view (such as a layout), this works: `lookup_context.template_exists?("navbar", controller._prefixes, :partial)`. This tells me if the current template rendering this layout has the stated "navbar" partial, and if so I can render it. I pass `:partial` just to be explicit about what that boolean is -- `:partial` is truthy. Thanks for the `_prefixes` bit, @Flackou! – pdobb Dec 20 '13 at 20:31
  • Replace `_prefixes` with `nil` if you're calling a partial that's in a different parent directory. – ben May 01 '15 at 19:39
8

I know this has been answered and is a million years old, but here's how i ended up fixing this for me...

Rails 4.2

First, i put this in my application_helper.rb

  def render_if_exists(path_to_partial)
    render path_to_partial if lookup_context.find_all(path_to_partial,[],true).any?
  end

and now instead of calling

<%= render "#{dynamic_path}" if lookup_context.find_all("#{dynamic_path}",[],true).any? %>

i just call <%= render_if_exists "#{dynamic_path}" %>

hope that helps. (haven't tried in rails3)

afxjzs
  • 982
  • 3
  • 10
  • 19
8

I have used this paradigm on many occasions with great success:

<%=
  begin
    render partial: "#{dynamic_partial}"
  rescue ActionView::MissingTemplate
    # handle the specific case of the partial being missing
  rescue
    # handle any other exception raised while rendering the partial
  end
%>

The benefit of the code above is that we can handle tow specific cases:

  • The partial is indeed missing
  • The partial exists, but it threw an error for some reason

If we just use the code <%= render :partial => "#{dynamic_partial}" rescue nil %> or some derivative, the partial may exist but raise an exception which will be silently eaten and become a source of pain to debug.

br3nt
  • 9,017
  • 3
  • 42
  • 63
8

What about your own helper:

def render_if_exists(path, *args)
  render path, *args
rescue ActionView::MissingTemplate
  nil
end
Andrey
  • 180
  • 2
  • 6
4

This works for me in Rails 6.1:

<% if lookup_context.exists?("override_partial", ['path/after/app/views'], true) %>
  <%= render partial: "path/after/app/views/override_partial" %>
<% else %>
  <%= render partial: "default_partial" %>
<% end %>

Here I have my partial nested some levels deeper than normal (app/views/path/after/app/views/_override_partial) so that's why I'm adding it as the prefixes array, but you can use lookup_context.prefixes instead if you don't need it.

I could have also used prepend_view_path on the controller. It's up to you :)

Sergio Gonzalez
  • 1,851
  • 1
  • 12
  • 12