Rails:在数据库中没有元素时显示消息的优雅方式

68

我意识到我写了很多类似于这个的代码:

<% unless @messages.blank? %>
  <% @messages.each do |message|  %>
    <%# code or partial to display the message %>
  <% end %>
<% else %>
  You have no messages.
<% end %>
Ruby和/或Rails中是否有任何结构可以让我跳过第一个条件?这样当迭代器/循环不进入甚至一次时,它就会被执行。例如:
<% @messages.each do |message| %>
  <%# code or partial to display the message %>
<% and_if_it_was_blank %>
  You have no messages.
<% end %>
10个回答

167

你也可以这样写:

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

6
我喜欢这个答案。在所有我自己尝试过的答案中,我选择了这一个,它非常简洁明了,易于理解。 - James F
1
非常棒的答案,非常简洁。 - Shane
7
在 Haml/Slim 中你会如何做这样的事情? - DickieBoy
谢谢!被采纳的答案更符合Rails的风格(也许正因为这个原因应该被采纳),但是这个答案将在我构建原型时节省我很多时间。 - user435779
这个功能叫什么? - onmyway133
这正是我正在寻找的! - mArtinko5MB

64
如果您使用:collection参数来呈现例如render :partial => 'message', :collection => @messages,那么对于空集合的呼叫将返回nil。这可以并入||表达式中。
<%= render(:partial => 'message', :collection => @messages) || 'You have no messages' %>

如果你之前没有遇到过的话,:collection 会使用同一个局部变量去渲染集合中的每一个元素,并将 @messages 中的每个元素在构建完整响应时通过局部变量 message 提供。你还可以使用 :spacer_template => "message_divider" 来指定要在每个元素之间呈现的分隔符。

1
太好了...那么在集合渲染之前和之后呢? 假设您想要在部分渲染之前和之后使用<ul>和</ul>或<tr></tr>标签对,但仅当@messages不为空时。例如-> <p><ul><li>message1</li><message2></ul><p>是@messages!= nil OR <p><ul>没有消息!<p> - mataal
3
我认为费尔南多·艾伦的解决方案应该被添加到这个答案中作为可能的替代方案,因为人们可能会跳过它,因为它不是“最佳答案”。 - arikfr
1
只是一个可能有用的小提示。为了使这个语法起作用,您必须像上面所示一样在“partial”赋值周围使用括号。如果没有它们,部分呈现正确,但条件消息不会。 - pruett

18

我很惊讶我的最爱答案不在这里。有一个接近的答案,但我不喜欢裸文本和使用content_for很别扭。试试这个:

  <%= render(@user.recipes) || content_tag("p") do %>
    This user hasn't added any recipes yet!
  <% end %>

1
这是到目前为止我最喜欢的答案。 - dkubb
7
重要提示:不要忘记在render函数后加括号,否则 || 运算符将会应用到集合本身而不是 render 函数的结果上。我之前也遇到过这个问题,因为没有加括号,导致代码无法正常工作(一开始)。 - D-side
1
这是我一直在寻找的优雅解决方案 :) - markquezada

17

6

您可以创建一些自定义助手函数。下面的示例只是一个例子。

# 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 %>

3
作为说明,如果您正在寻求表达效率,那么您可以遍历一个空数组:
<% @messages.each do |message|  %>
  <%# code or partial to dispaly the message %>
<% end %>
<% if (@messages.blank?) %>
  You have no messages.
<% end %>

虽然这并没有处理@messages为空的情况,但它应该适用于大多数情况。在本应是常规视图的地方引入不规则扩展可能会使本来简单的事情变得复杂。

更好的方法可能是定义一个partial和helper来渲染“空”部分,如果这些部分相对复杂:

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

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

你可能会将其定义为:

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

1

虽然这是一个旧话题,但我并不是特别喜欢其中任何一种方法,所以在Rails 3.2上尝试了一下,发现了这个替代方案:

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

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

或者,如果您需要像我一样带有部分路径的更详细的渲染:

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

这样,您可以使用任何HTML /视图逻辑来设计“无消息”部分,并使其易于阅读。

0
以下解决方案适用于我,因为我想要具有 colspan 属性的 trtd
<%= render(@audit_logs) || content_tag(:tr, content_tag(:td, 'No records',colspan: "11"))%>

0

那段代码可以缩短为:

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

评论:

formatted_msg() - 辅助方法,给消息添加格式

msg_delimiter - 变量,包含分隔符,如"\n"或"<br />"

顺便说一句,我建议使用empty?方法而不是blank?来检查数组,因为 a) 它的名称更简洁 :) b) blank?是一个只在Rails之外无法使用的ActiveSupport扩展方法。


谢谢您的建议,但我更喜欢使用 blank?因为我不必检查对象是否为空,而且这只是Rails特定扩展,在这种情况下并不会对我造成太多麻烦。 - Jakub Troszok

0
您可以将两种情况拆分为不同的模板:如果存在消息,则使用一种模板,如果不存在消息,则使用另一种模板。在控制器操作中(MessagesController#index 可能),作为最后一个语句添加以下内容:
render :action => 'index_empty' if @messages.blank?

如果没有消息,它将显示app/views/messages/index_empty.html.erb。如果有消息,它将继续并像往常一样显示app/views/messages/index.html.erb
如果您需要在多个操作中使用此功能,可以将其优雅地重构为以下帮助程序方法(未经测试):
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

使用这个方法,你只需要在任何控制器操作的结尾处放置render_action_or_empty(@var),它将显示“action”模板或“action_empty”模板(如果你的集合为空)。也很容易将其与局部模板一起使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接