concat
concat(string)The preferred method of outputting text in your views is to use the <%= "text" %> eRuby syntax. The regular puts and print methods do not operate as expected in an eRuby code block. If you absolutely must output text within a non-output code block (i.e., <% %>), you can use the concat method.
<% concat "hello" %> is equivalent to <%= "hello" %> <% unless signed_in? concat link_to("Sign In", action: :sign_in) end %> is equivalent to <% unless signed_in? %> <%= link_to "Sign In", action: :sign_in %> <% end %>
6Notes
helper method to partial
concat can be useful for rendering a block to a partial from a helper:
def block_to_partial(partial_name, options = {}, &block)
options.merge!(:body => capture(&block))
concat(render(:partial => partial_name, :locals => options), block.binding)
end
This would be particularly useful if you had some partial to help you out with rounded corners, for example. So, in your helper:
def rounded_corners &block block_to_partial("shared/rounded_corners", {}, &block) end
In your view you could have something like:
<% rounded_corners do -%> This text is surrounded by rounded corners <% end -%>
You would have to create some partial in app/views/shared/rounded_corners.html.erb
And it would look something like:
Re: Helper method taking a block
The same using the ActionView::Helpers::TagHelper#content_tag and ActionView::Helpers::CaptureHelper#capture methods:
def render_tree(collection, &block)
concat(
content_tag(:ul,
collection.collect { |item|
content_tag(:li, capture(item, &block))
}.join("\
") ), block.binding ) end
The benefit is that it's easier to improve with html attributes (just add a hash of options to the +content_tag+ call) and it makes just one call to concat (which probably makes it faster).
Helper method taking a block
Following the similar egzample by autonomous, here's a simpler version when you just need to write a flexible helper method that takes a block.
For example, suppose you have a method that renders a tree:
def render_tree(ary, &block)
concat("<ul>", block.binding)
for elem in ary
concat("<li>", block.binding)
yield elem
concat("</li>", block.binding)
end
concat("</ul>", block.binding)
end
You can use it in your view, eg:
<% render_tree(@objects) do |elem| -%>
<%= elem.title -%>
<%= link_to 'delete', elem -%>
<% end -%>
that would return for egzample:
- Test title /elems/1
=== Testing concat
To test such helper methods, use the following pattern (a utility method added to your Rspec/unit test suite:
def render_for(root, options = {})
_erbout = ''
render_tree(root, options) do |node|
_erbout.concat(node.title)
end
_erbout
end
and test like this (RSpec example):
it "should return abc" do
render_for(object).should == 'abc'
end
Iterate and join blocks
Following LacKac's idea, we can write render_join (useful to render a collection with a small chunks of code, where a render :partial + :spacer_template would be overkill):
def render_join(collection, join_string, &block)
output = collection.collect do |item|
capture(item, &block)
end.join(join_string)
concat(output, block.binding)
end
An example of use:
<% render_join(@items, '<br />') do |item| %>
<p>Item title: <%= item.title %></p>
<% end %>
Binding parameter deprecated in > 2.2
Supplying the binding argument produces a deprecation warning in 2.2 and newer:
DEPRECATION WARNING: The binding argument of #concat is no longer needed. Please remove it from your views and helpers.
Capturing blocks after >2.2
After 2.2, you can omit the do ...end block and simply use the &block variable directly: concat(content_tag(:div, :class => "wrapped_content") do capture(&block) end, block.binding)
becomes simply:
concat(content_tag(:div, capture(&block), :class => "wrapped_content"))