在一个 ERB 块中渲染多个表达式的输出

Render output of multiple expressions in one ERB block

我有一个像这样的助手:

if current_user.find_voted_items(vote_scope: :inspired).include?(post)
   link_to vote_inspired_post_path(post, vote_scope: :inspired), method: :post, data: { confirm: 'Are you sure this post Inspires you?' }, class: "btn btn-default" do
    "<i class='fa fa-lightbulb-o'></i> <br />Inspired".html_safe
   end
   link_to vote_happy_post_path(post, vote_scope: :happy), method: :post, data: { confirm: 'Are you sure this post makes you happy?' }, class: "btn btn-success" do
    "<i class='fa fa-smile-o'></i> <br />Happy".html_safe
   end
   link_to vote_disappointed_post_path(post, vote_scope: :disappointed), method: :post, data: { confirm: 'Are you sure this post disappointed you?' }, class: "btn btn-info" do
    "<i class='fa fa-meh-o'></i> <br />Disappointed".html_safe
   end
   link_to vote_upset_post_path(post, vote_scope: :upset), method: :post, data: { confirm: 'Are you sure this post upsets you?' }, class: "btn btn-inverse" do
    "<i class='fa fa-frown-o'></i> <br />Upset".html_safe
   end
end

我需要呈现所有链接及其嵌套的 <i> 标签 - 但出于某种原因,此版本只呈现最后一行。

所有这些都在一个名为 show_vote_buttons(post) 的方法中,在视图中是这样调用的:<%= show_vote_buttons(@post) %>

解决这个问题的最佳方法是什么?

基本上,这背后的原因是 <%= %> 渲染了 show_vote_buttons 方法的输出。此方法没有明确 return 任何内容,因此最后计算的表达式是 returned,在您的情况下,是最后的 link_to 输出。

在一般情况下,如果您没有使用辅助方法而只是粘贴它的主体而不是多次 link_to 调用,您会得到相同的行为。原因类似:<%= %> 不渲染每个 link_to,它在 <%= %> 中执行代码,然后输出最后一个计算表达式的结果。

我看到两种不同的方法来改变输出:

  1. 辅助方法:连接所有求值表达式的输出并打印为一个字符串:
    1.1 在每个 link_to;
    周围使用 << 和圆括号 () 1.2 创建一个带双引号 " 的字符串,并在每个 link_to 输出中插入 #{}
    1.3 使用 concat;
  2. 部分视图:从现有的辅助方法构建一个单独的视图,并使用<%= %>输出每个link_to

P.S。在测试了所有四种方法之后,我得出一个结论(这里是个人意见),第二种方法更可取,至少因为它在视图中保持渲染并避免看起来混乱的串联。例如,在 Devise gem 中使用了类似的方法,其中所有共享链接都位于 app/views/devise/shared/_links.html.erb 部分。

@suslov 围绕这个答案完美地解释了原因。元编程可能会使代码保持干燥:

DATA = {
  inspired: { ico: 'fa-lightbulb-o', 
              msg: 'Are you sure this post Inspires you?',
              btn: 'btn-default' },
  happy: {    ico: 'fa-smile-o', 
              msg: 'Are you sure this post makes you happy?',
              btn: 'btn-success' },
  ... }

if current_user.find_voted_items(vote_scope: :inspired).include?(post)
  DATA.map do |k, v|
   instance_eval %Q{
     link_to vote_#{k}_post_path(post, vote_scope: :#{k}), 
               method: :post, 
               data: { confirm: #{v[:msg]} }, 
               class: "btn #{v[:btn]}" do
       "<i class='fa #{v[:ico]'></i> <br />#{k.capitalize}".html_safe
     end
   }.join($/) # return them all, concatenated with separator
  end
end

but for some reason, this version is just rendering the last line.

那个原因是因为在Ruby中,如果一个方法没有明确的return,那么方法的returned值是最后一行的值在方法退出之前执行。如果你想 return 来自一个方法的多个值,请使用 return 关键字并用逗号分隔你的值。

#will return an array [1,2,3]
def some_method
 return 1,2,3
end

此外,我同意其他答案,即您的代码应该更 DRY,但我只是想澄清您最初的问题。

试试这个代码

if current_user.find_voted_items(vote_scope: :inspired).include?(post)
concat(link_to vote_inspired_post_path(post, vote_scope: :inspired), method: :post, data: { confirm: 'Are you sure this post Inspires you?' }, class: "btn btn-default" do
  safe_concat(content_tag(:i, "Inspired", calss: "fa fa-lightbulb-o"))
end)
concat(link_to vote_happy_post_path(post, vote_scope: :happy), method: :post, data: { confirm: 'Are you sure this post makes you happy?' }, class: "btn btn-success" do
  safe_concat(content_tag(:i, "Happy", calss: "fa fa-smile-o"))
end)
concat(link_to vote_disappointed_post_path(post, vote_scope: :disappointed), method: :post, data: { confirm: 'Are you sure this post disappointed you?' }, class: "btn btn-info" do
  safe_concat(content_tag(:i, "Disappointed", calss: "fa fa-meh-o"))
end)
concat(link_to vote_upset_post_path(post, vote_scope: :upset), method: :post, data: { confirm: 'Are you sure this post upsets you?' }, class: "btn btn-inverse" do
  safe_concat(content_tag(:i, "Upset", calss: "fa fa-frown-o"))
end)
end

注:没测试过