Module: ActionView::Helpers::CaptureHelper

Included in:
ActionView::Helpers, TagHelper, TagHelper::TagBuilder
Defined in:
lib/action_view/helpers/capture_helper.rb

Overview

Action View Capture Helpers

CaptureHelper exposes methods to let you extract generated markup which can be used in other parts of a template or layout file.

It provides a method to capture blocks into variables through #capture and a way to capture a block of markup for use in a layout through #content_for.

As well as provides a method when using streaming responses through #provide. See ActionController::Streaming for more information.

Instance Method Summary collapse

Instance Method Details

#capture(*args, &block) ⇒ Object

The capture method extracts part of a template as a string object. You can then use this object anywhere in your templates, layout, or helpers.

The capture method can be used in ERB templates…

<% @greeting = capture do %>
  Welcome to my shiny new web page!  The date and time is
  <%= Time.now %>
<% end %>

…and Builder (RXML) templates.

@timestamp = capture do
  "The current timestamp is #{Time.now}."
end

You can then use that variable anywhere else. For example:

<html>
<head><title><%= @greeting %></title></head>
<body>
<b><%= @greeting %></b>
</body>
</html>

The return of capture is the string generated by the block. For Example:

@greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"


47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/action_view/helpers/capture_helper.rb', line 47

def capture(*args, &block)
  value = nil
  @output_buffer ||= ActionView::OutputBuffer.new
  buffer = @output_buffer.capture { value = yield(*args) }

  case string = buffer.presence || value
  when OutputBuffer
    string.to_s
  when ActiveSupport::SafeBuffer
    string
  when String
    ERB::Util.html_escape(string)
  end
end

#content_for(name, content = nil, options = {}, &block) ⇒ Object

Calling content_for stores a block of markup in an identifier for later use. In order to access this stored content in other templates, helper modules or the layout, you would pass the identifier as an argument to content_for.

Note: yield can still be used to retrieve the stored content, but calling yield doesn’t work in helper modules, while content_for does.

<% content_for :not_authorized do %>
  alert('You are not authorized to do that!')
<% end %>

You can then use content_for :not_authorized anywhere in your templates.

<%= content_for :not_authorized if current_user.nil? %>

This is equivalent to:

<%= yield :not_authorized if current_user.nil? %>

content_for, however, can also be used in helper modules.

module StorageHelper
  def stored_content
    content_for(:storage) || "Your storage is empty"
  end
end

This helper works just like normal helpers.

<%= stored_content %>

You can also use the yield syntax alongside an existing call to yield in a layout. For example:

<%# This is the layout %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>My Website</title>
  <%= yield :script %>
</head>
<body>
  <%= yield %>
</body>
</html>

And now, we’ll create a view that has a content_for call that creates the script identifier.

<%# This is our view %>
Please login!

<% content_for :script do %>
  <script>alert('You are not authorized to view this page!')</script>
<% end %>

Then, in another view, you could to do something like this:

<%= link_to 'Logout', action: 'logout', remote: true %>

<% content_for :script do %>
  <%= javascript_include_tag :defaults %>
<% end %>

That will place script tags for your default set of JavaScript files on the page; this technique is useful if you’ll only be using these scripts in a few views.

Note that content_for concatenates (default) the blocks it is given for a particular identifier in order. For example:

<% content_for :navigation do %>
  <li><%= link_to 'Home', action: 'index' %></li>
<% end %>

And in another place:

<% content_for :navigation do %>
  <li><%= link_to 'Login', action: 'login' %></li>
<% end %>

Then, in another template or layout, this code would render both links in order:

<ul><%= content_for :navigation %></ul>

If the flush parameter is true content_for replaces the blocks it is given. For example:

<% content_for :navigation do %>
  <li><%= link_to 'Home', action: 'index' %></li>
<% end %>

<%# Add some other content, or use a different template: %>

<% content_for :navigation, flush: true do %>
  <li><%= link_to 'Login', action: 'login' %></li>
<% end %>

Then, in another template or layout, this code would render only the last link:

<ul><%= content_for :navigation %></ul>

Lastly, simple content can be passed as a parameter:

<% content_for :script, javascript_include_tag(:defaults) %>

WARNING: content_for is ignored in caches. So you shouldn’t use it for elements that will be fragment cached.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/action_view/helpers/capture_helper.rb', line 166

def content_for(name, content = nil, options = {}, &block)
  if content || block_given?
    if block_given?
      options = content if content
      content = capture(&block)
    end
    if content
      options[:flush] ? @view_flow.set(name, content) : @view_flow.append(name, content)
    end
    nil
  else
    @view_flow.get(name).presence
  end
end

#content_for?(name) ⇒ Boolean

content_for? checks whether any content has been captured yet using content_for.

Useful to render parts of your layout differently based on what is in your views.

<%# This is the layout %>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>My Website</title>
  <%= yield :script %>
</head>
<body class="<%= content_for?(:right_col) ? 'two-column' : 'one-column' %>">
  <%= yield %>
  <%= yield :right_col %>
</body>
</html>

Returns:

  • (Boolean)


209
210
211
# File 'lib/action_view/helpers/capture_helper.rb', line 209

def content_for?(name)
  @view_flow.get(name).present?
end

#provide(name, content = nil, &block) ⇒ Object

The same as content_for but when used with streaming flushes straight back to the layout. In other words, if you want to concatenate several times to the same buffer when rendering a given template, you should use content_for, if not, use provide to tell the layout to stop looking for more contents.

See ActionController::Streaming for more information.



188
189
190
191
192
# File 'lib/action_view/helpers/capture_helper.rb', line 188

def provide(name, content = nil, &block)
  content = capture(&block) if block_given?
  result = @view_flow.append!(name, content) if content
  result unless content
end

#with_output_buffer(buf = nil) ⇒ Object

Use an alternate output buffer for the duration of the block. Defaults to a new empty string.



215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/action_view/helpers/capture_helper.rb', line 215

def with_output_buffer(buf = nil) # :nodoc:
  unless buf
    buf = ActionView::OutputBuffer.new
    if output_buffer && output_buffer.respond_to?(:encoding)
      buf.force_encoding(output_buffer.encoding)
    end
  end
  self.output_buffer, old_buffer = buf, output_buffer
  yield
  output_buffer
ensure
  self.output_buffer = old_buffer
end