为什么视图中的块会呈现两次?

Max*_*ing 6 haml ruby-on-rails erb helpers

我在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>
Run Code Online (Sandbox Code Playgroud)

这是我的帮手:

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
Run Code Online (Sandbox Code Playgroud)

这是我的看法:

<%= panel :default do |p| %>
  <%= p.header 'Contacts' %>
  <%= p.body do %>
    <p>Test</p>
  <% end %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

这是呈现的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>
Run Code Online (Sandbox Code Playgroud)

任何想法为什么会发生这种情况以及如何解决它?我可能只是误解了一些关于视图块的东西.

谢谢

编辑

我可以通过使用这个body方法让它运行:

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

其中@helper是从主辅助模块ApplicationHelper中作为self传递给PanelHelper初始值设定项的.我也在调用p.body时删除=因为我们直接写入缓冲区.

JTG*_*JTG 6

<%= p.body do %>
  <p>Test</p>
<% end %>
Run Code Online (Sandbox Code Playgroud)

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

但这可以通过nilbody函数末尾放置来证明:

def body(&block)
  content_tag(:div, block.call, class: 'panel-body')
  nil
end
Run Code Online (Sandbox Code Playgroud)

将导致 <p>Test</p>被放置在代码中( 的结果block.call,但不是 content_tag 调用)。

但是,更改block.call"<p>Test</p>"

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

将导致您的 HTML 中没有任何内容。因此,yield/block.call视图中的帮助器产生了一些意想不到的后果。所以这基本上就是你看到<p>Test</p>两次的原因。

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

def body(&block)
  content_tag :div, class: 'panel-body' do
    block.call
  end
end
Run Code Online (Sandbox Code Playgroud)

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

content_tag(:div, capture(&block), class: 'panel-body')
Run Code Online (Sandbox Code Playgroud)


Pra*_*thy 2

使用

<% p.body do %>
  <p>Test</p>
<% end %> 
Run Code Online (Sandbox Code Playgroud)

ie<% p.body do %>而不是会抑制视图中的<%= p.body %>第一个。<p>Test</p>

编辑:

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

<%= p.body do %>
  <p>Test</p>
<% end %> 
Run Code Online (Sandbox Code Playgroud)

会给出你想要的输出。