68

I realized that I'm writing a lot of code similar to this one:

<% unless @messages.blank? %>
  <% @messages.each do |message|  %>
    <%# code or partial to display the message %>
  <% end %>
<% else %>
  You have no messages.
<% end %>

Is there any construct in Ruby and/or Rails that would let me skip that first condition? So that would be executed when iterator/loop won't enter even once? For example:

<% @messages.each do |message| %>
  <%# code or partial to display the message %>
<% and_if_it_was_blank %>
  You have no messages.
<% end %>
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
Jakub Troszok
  • 99,267
  • 11
  • 41
  • 53

10 Answers10

167

You could also write something like this:

<% if @messages.each do |message| %>
  <%# code or partial to display the message %>
<% end.empty? %>
  You have no messages.
<% end %>
Fernando Allen
  • 1,701
  • 2
  • 10
  • 6
64

If you use the :collection parameter to render e.g. render :partial => 'message', :collection => @messages then the call to render will return nil if the collection is empty. This can then be incorporated into an || expression e.g.

<%= render(:partial => 'message', :collection => @messages) || 'You have no messages' %>

In case you haven't come across it before, render :collection renders a collection using the same partial for each element, making each element of @messages available through the local variable message as it builds up the complete response. You can also specify a divider to be rendered in between each element using :spacer_template => "message_divider"

mikej
  • 65,295
  • 17
  • 152
  • 131
  • 1
    great... how about a before collection and after collection? say you want to have
      and
    or tag pairs before and after the partial rendering but only if the @messages ar enot empty. examples- >

    • message1

    is @messages!=nil OR

      no messages!

    – mataal Jun 22 '09 at 20:48
  • 3
    I think that Fernando Allen's solution should be added to this answer as possible alternative, as people might skip it because it's not the "best answer". – arikfr Feb 13 '11 at 14:35
  • 1
    just a quick note that may be helpful. in order for this syntax to work, you must use parentheses around the `partial` assignment as shown above. without them, the partial renders correctly, but the conditional message does not – pruett Jul 18 '12 at 17:17
18

I'm surprised my favorite answer isn't up here. There is an answer thats close, but I don't like bare text and using content_for is klunky. Try this one on for size:

  <%= render(@user.recipes) || content_tag("p") do %>
    This user hasn't added any recipes yet!
  <% end %>
David Bock
  • 681
  • 4
  • 8
  • 1
    This is my favourite answer so far. – dkubb Nov 05 '13 at 23:45
  • 7
    **Important:** don't forget parentheses for `render`, otherwise the `||` will apply not to the result of `render`, but to the collection itself. I've just written the same myself, but it didn't work (at first) because of this. – D-side Dec 30 '14 at 20:12
  • 1
    This was the elegant solution I was looking for :) – markquezada Oct 28 '15 at 03:05
17

One way is to do something like:

<%= render(:partial => @messages) || render('no_messages') %>

Edit:

If I remember correctly this was made possible by this commit:

http://github.com/rails/rails/commit/a8ece12fe2ac7838407954453e0d31af6186a5db

jonnii
  • 28,019
  • 8
  • 80
  • 108
6

You can create some custom helper. The following one is just an example.

# application_helper.html.erb
def unless_empty(collection, message = "You have no messages", &block)
  if collection.empty?
    concat(message)
  else
    concat(capture(&block))
  end
end

# view.html.erb
<% unless_empty @messages do %>
  <%# code or partial to dispaly the message %>
<% end %>
Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
3

As a note, you may as well just iterate over an empty array if you're looking for efficiency of expression:

<% @messages.each do |message|  %>
  <%# code or partial to dispaly the message %>
<% end %>
<% if (@messages.blank?) %>
  You have no messages.
<% end %>

While this does not handle @messages being nil, it should work for most situations. Introducing irregular extensions to what should be a routine view is probably complicating an otherwise simple thing.

What might be a better approach is to define a partial and a helper to render "empty" sections if these are reasonably complex:

<% render_each(:message) do |message|  %>
  <%# code or partial to dispaly the message %>
<% end %>

# common/empty/_messages.erb
You have no messages.

Where you might define this as:

def render_each(item, &block)
  plural = "#{item.to_s.pluralize}"
  items = instance_variable_get("@#{plural}")
  if (items.blank?)
    render(:partial => "common/empty/#{plural}")
  else
    items.each(&block)
  end
end
tadman
  • 208,517
  • 23
  • 234
  • 262
1

Old topic but I didn't really like any of these so playing around on Rails 3.2 I figured out this alternative:

<% content_for :no_messages do %>
  <p>
    <strong>No Messages Found</strong>
  </p>
<% end %>

<%= render @messages || content_for(:no_messages) %>

Or if you need a more verbose render with partial path like I did:

<%= render(:partial => 'messages', 
     :collection => @user.messages) || content_for(:no_messages) %>

This way you can style the "no messages" part with whatever HTML / view logic you want and keep it nice a easy to read.

Kansha
  • 570
  • 4
  • 12
0

That code can be shortened to:

<%= @messages.empty? ? 'You have no messages.' : @messages.collect { |msg| formatted_msg(msg) }.join(msg_delimiter) %>

Comments:

formatted_msg() - helper method which adds formatting to the message

msg_delimiter - variable containing delimiter like "\n" or "<br />"

BTW I'd suggest to use empty? method instead of blank? for checking an array, because a) its name is more concise :) and b) blank? is an ActiveSupport extension method which won't work outside Rails.

Lukas Stejskal
  • 2,542
  • 19
  • 30
  • Thanks for suggesion but I prefer to use blank? because i don't have to check if object is not nil, and being only rails specific extension doesn't bother me much in this case. – Jakub Troszok Jun 22 '09 at 16:34
0

You could split up your two cases into different templates: one if messages exist and one if no message exist. In the controller action (MessagesController#index probably), add as the last statement:

render :action => 'index_empty' if @messages.blank?

If there are no messages, it'll display app/views/messages/index_empty.html.erb. If there are messages, it'll fall through and display app/views/messages/index.html.erb as usual.

If you need this in more than just one action, you can nicely refactor it into a helper method like the following (untested):

def render_action_or_empty (collection, options = {})
    template = params[:template] || "#{params[:controller]}/#{params[:action]}"
    template << '_empty' if collection.blank?
    render options.reverse_merge { :template => template }
end

With this, you just need to put render_action_or_empty(@var) at the end of any controller action and it'll display either the 'action' template or the 'action_empty' template if your collection is empty. It should also be easy to make this work with partials instead of action templates.

Zargony
  • 9,615
  • 3
  • 44
  • 44
0

Below solution works for me because I want tr and td with colspan property:

<%= render(@audit_logs) || content_tag(:tr, content_tag(:td, 'No records',colspan: "11"))%>
matth
  • 6,112
  • 4
  • 37
  • 43
Jigar Bhatt
  • 4,217
  • 2
  • 34
  • 42