【问题标题】:Why is a block from a view rendered twice?为什么视图中的块被渲染两次?
【发布时间】:2015-03-01 00:43:14
【问题描述】:

我用 Ruby on Rails 编写的一个 Web 应用程序有很多使用的内容面板。我正在尝试编写帮助程序以使用更简洁的语法呈现这些面板。我对编写视图助手不是很熟悉,所以这可能很简单。但是,当尝试使用传递给块中的助手的“html”(erb 代码)时,我得到了奇怪的结果,其中块被渲染了两次。

相关代码如下。此示例使用 ERB,因为我对其进行了简化以避免任何可能的 HAML 问题,但我在使用 HAML 时得到了相同的结果。

我要渲染的 HTML 是:

<section class="panel panel-default">
  <header class="panel-heading">Contacts</header>

  <div class="panel-body">
    <p>Test</p>
  </div>
</section>

这是我的助手:

module ApplicationHelper
  def panel(type = :default, &block)
    tag = content_tag(:section, class: "panel panel-#{type.to_s}") do
      block.call PanelHelper.new
    end

    tag
  end

  class PanelHelper
    include ActionView::Helpers::TagHelper
    include ActionView::Context
    include ActionView::Helpers::CaptureHelper

    def header(text = nil, &block)
      if block_given?
        tag = content_tag(:header, block.call, class: 'panel-heading')
      elsif text
        tag = content_tag(:header, text, class: 'panel-heading')
      else
        raise ArgumentError, 'Either a block must be given, or a value must be provided for the text parameter.'
      end

      tag
    end

    def body(&block)
      content_tag(:div, block.call, class: 'panel-body')
    end
  end
end

这是我的看法:

<%= panel :default do |p| %>
  <%= p.header 'Contacts' %>
  <%= p.body do %>
    <p>Test</p>
  <% end %>
<% end %>

这是呈现的 HTML:

<section class="panel panel-default">
  <header class="panel-heading">Contacts</header>

  <p>Test</p>
  <div class="panel-body">
    &lt;p&gt;Test&lt;/p&gt;
  </div>
</section>

任何想法为什么会发生这种情况以及如何解决它?我可能只是对视图块有误解。

谢谢

编辑

我可以通过使用这个 body 方法让它发挥作用:

def body(&block)
  @helper.concat tag(:div, class: 'panel-body').html_safe
  block.call
  @helper.concat '</div>'.html_safe
end

其中@helper 是PanelHelper 初始化程序中作为来自主帮助程序模块ApplicationHelper 的self 传递的。此外,我在调用 p.body 时删除了 =,因为我们正在直接写入缓冲区。

【问题讨论】:

    标签: ruby-on-rails haml erb helpers


    【解决方案1】:
    <%= p.body do %>
      <p>Test</p>
    <% end %>
    

    所以,&lt;p&gt;Test&lt;/p&gt; 出现了两次(有点),因为第一个实例是在 ApplicationModule 帮助器的 body 代码内调用 yield(或者在您的情况下为 block.call)的结果。根据this railscast,视图中的块与普通块的工作方式不同,因为 yield auto 将块调用的结果插入到 HTML 中(我仍然不确定为什么,但我正在尝试弄清楚)。

    但这可以通过将nil 放在body 函数的末尾来证明:

    def body(&block)
      content_tag(:div, block.call, class: 'panel-body')
      nil
    end
    

    将导致&lt;p&gt;Test&lt;/p&gt; 被放置在代码中(block.call 的结果,而不是 content_tag 调用的结果)。

    但是,将 block.call 更改为 "&lt;p&gt;Test&lt;/p&gt;"

    def body(&block)
      content_tag(:div, "<p>Test</p>", class: 'panel-body')
      nil
    end
    

    将导致您的 HTML 中没有任何内容。因此,视图助手中的yield/block.call 会产生一些意想不到的后果。所以这就是为什么你看到&lt;p&gt;Test&lt;/p&gt; 两次的原因。

    解决方案,您可以按照@PrakashMurthy 的建议进行操作,然后像这样将块传递给 content_tag

    def body(&block)
      content_tag :div, class: 'panel-body' do
        block.call
      end
    end
    

    这是可行的,因为您的助手没有屈服于该块,而是将其传递给与yield 将代码插入模板的行为不同的方法。 您还可以使用capture 方法,该方法获取块的结果并将其作为字符串返回。

    content_tag(:div, capture(&block), class: 'panel-body')
    

    【讨论】:

    • 您的回答确实帮助我更好地理解了我的问题,但是您提出的两种方法具有相同的结果......无论哪种方式 block.call 都被调用,所以我得到了意外的输出。如果我将 def body(&block) content_tag :div, class: 'panel-body' do "

      Test

      ".html_safe end end 放在 body 方法中,我确实得到了预期的输出(尽管显然它是不是动态的)...所以我需要弄清楚如何在不立即将其插入页面的情况下获取块的内容。
    【解决方案2】:

    使用

    <% p.body do %>
      <p>Test</p>
    <% end %> 
    

    &lt;% p.body do %&gt; 而不是 &lt;%= p.body %&gt; 会抑制视图中的第一个 &lt;p&gt;Test&lt;/p&gt;

    编辑:

    def body(&block)
      content_tag :div, class: 'panel-body' do
        block.call
      end
    end
    
    <%= p.body do %>
      <p>Test</p>
    <% end %> 
    

    会给出你想要的输出。

    【讨论】:

    • 是的,= 需要删除。但是最后我只得到了块的内容渲染(没有
      包裹它)。
    • 更新了答案以显示预期的输出。在此过程中提高了我对blockscontent_tag 的理解:-)
    • 您发布的更新方法仍然提供相同(意外/不正确)的输出。
    猜你喜欢
    相关资源
    最近更新 更多
    热门标签