Best practices for conditional display in views?

Hi --

Hi.

I am writing an application that has a lot of boolean conditional
display logic, like this:

<% if user.description then %>
<p class="css_class"><%= user.description %></p>
<% end %>

Often the displayed content is more complex than the above, and to clean
up my views I am trying to pull a lot of this sort of thing into
partials.

However, the problem arises that I will frequently need to format the
same information in different ways. For example, the above user
description appears as a standalone item so it can logically be wrapped
in a <p> tag. But it might be an inline item that should appear in a
<span>, or a list item that should appear in <li> tags.

That means that the enclosing tag (and, typically, CSS class reference)
need to be pulled back out into the view, like:

<li class="css_class">
<%= render :partial => "user_description" %>
</li>

But this will result in empty <p> or <span> or <li> tags being rendered
in the event that the conditional test (in the partial) returns false,
which happens frequently. So that suggests I should put the conditional
logic back in the view and remove it from the partial altogether:

<% if user.description then %>
<p class="css_class">
<%= render :partial => "user_description" %>
</p>
<% end %>

...which sort of defeats the purpose of the partial.

Is there any recognized best practice for handling this situation? What
would be handy is if the render method could take an :if parameter. Or I
suppose I could write a helper that forms a wrapper tag given a tag name
and class, and include the helper in lots of partials:

<%= render :partial => "user_description", :locals => { :tag_name => li,
:class => "css_class" %>

But I'm open to better suggestions.

I tend to do the second-to-last way you've shown. The last way, with
locals, is less appealing to me, as it has the same information but in
a somewhat overly abstracted form. I don't think putting the partial
in an HTML element defeats the purpose. Basically the purpose is
partly to un-spaghettify the view, and partly to reuse stretches of
template -- and doing it that way, you do both.

David

Another way may be to pass a variable into the partial, then render
appropriately:

<%= render :partial => "user_description", :wrap_in=> "p" %>
<%= render :partial => "user_description", :wrap_in=> "li" %>
<%= render :partial => "user_description", :wrap_in=> "span", :class =>
"my_class" %>

_user_description
  <% if user.description then %>
    <<%= params[:format] %> class="<%= params[:class] %>">
       <%= user.description %>
    </<%= params[:format] %>>
<% end %>

This would be better suited to an application helper and could do with
some error checking.

Sorry - ignore that params stuff - wrong part of MVC, but you get the
idea...

OK, ok, ok I need sleep:

<%= render :partial => "user_description", :locals => {:wrap_in=> "p"}
%>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"li"} %>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"span", :class =>
"my_class"} %>

_user_description
  <% if user.description then %>
    <<%= wrap_in %> class="<%= class %>">
       <%= user.description %>
    </<%= wrap_in %>>
<% end %>

A bit uglier than the first attempt (which required rewriting of the
render method), but it works.

/me gone to get coffee

Hi --

OK, ok, ok I need sleep:

<%= render :partial => "user_description", :locals => {:wrap_in=> "p"}
%>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"li"} %>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"span", :class =>
"my_class"} %>

_user_description
<% if user.description then %>
   <<%= wrap_in %> class="<%= class %>">
      <%= user.description %>
   </<%= wrap_in %>>
<% end %>

A bit uglier than the first attempt (which required rewriting of the
render method), but it works.

I'm trying to put my finger on why I don't like pushing the
conditional stuff out to the partial. Maybe it's that I always feel a
little queasy/guilty about putting conditionals in the views, rather
than the controller, so putting them in the partials feels even less
"pure" :slight_smile: Mind you, I do put conditionals in views (I'm not a
purist on that, if purism means not doing so), though if there's so
much conditional logic that it starts to bother me, I usually see that
as a sign that I should really have more than one template.

David

Hi all,

OK, ok, ok I need sleep:

<%= render :partial => "user_description", :locals => {:wrap_in=> "p"}
%>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"li"} %>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"span", :class =>
"my_class"} %>

> > > <%= tag :p, :class => "css_class", :wrap => render_to_string(:partial =>
> > > "user_description"), :if => user.description %>

I would go with view helpers, such as this:

# app/helpers/application_helper.rb
def wrap(tag, tag_options, *partial_args)
  content_tag(tag, render(*partial_args), tag_options)
end

# app/views/bla/bla.rhtml
<%= wrap(:p, {:class => 'a'}, :partial => 'user_description') if
@user.description %>

# app/views/shared/_user_description.rhtml
<%= user.description %>

Even your _user_description partial should probably be extracted to a
view helper. And having many of those will show you you really need
some other kind of view helper: one that's generic and is specific to
your domain.

Hope that helps !

Hi --

Hi all,

OK, ok, ok I need sleep:

<%= render :partial => "user_description", :locals => {:wrap_in=> "p"}
%>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"li"} %>
<%= render :partial => "user_description", :locals => {:wrap_in=>
"span", :class =>
"my_class"} %>

<%= tag :p, :class => "css_class", :wrap => render_to_string(:partial =>
"user_description"), :if => user.description %>

I would go with view helpers, such as this:

# app/helpers/application_helper.rb
def wrap(tag, tag_options, *partial_args)
content_tag(tag, render(*partial_args), tag_options)
end

# app/views/bla/bla.rhtml
<%= wrap(:p, {:class => 'a'}, :partial => 'user_description') if
@user.description %>

# app/views/shared/_user_description.rhtml
<%= user.description %>

Even your _user_description partial should probably be extracted to a
view helper. And having many of those will show you you really need
some other kind of view helper: one that's generic and is specific to
your domain.

I'd tend to keep the partial as a partial, not a helper. In fact
usually I end up turning helpers into partials, more than the other
way around.

I don't know... all of this seems like a lot to go through to avoid
having <p> or <li> in the view. Do you really need a hash-based
interface to the HTML, rather than the HTML itself? I'm interested in
what the advantages would be.

David