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