7

I have an ERB view with two blocks:

<%= test_h1 do %>
  <%= 'test1' %>
<% end -%>

<%= test_h2 do %>
  <%= 'test2' %>
<% end -%>

where test_h1 and test_h2 are similar helpers, but one is defined in a helper file, while another via helper_method in a controller:

module TestHelper
  def test_h1(&block)
    link_to '/url' do
      capture(&block)
    end
  end
end

class TestController < ApplicationController
  helper_method :test_h2

  def test_h2(&block)
    helpers.link_to '/url' do
      helpers.capture(&block)
    end
  end
end

test_h1 produces the expected result and test_h2 renders the inner template block first:

<a href="/url">test1</a>

test2<a href="/url"></a>

Why? What would be an idiomatic way to write test_h2 ?

Alexander Azarov
  • 12,235
  • 2
  • 45
  • 50

4 Answers4

3

I think both examples of views should be re-written as:

<%= test_h1 do %>
  <% 'test1' %>
<% end -%>

<%= test_h2 do %>
  <% 'test2' %>
<% end -%>

My understanding that '<%=' forces to render the output of the block to the output stream, that was not an intended behavior in these two examples

  • Look at the example of using blocks inside `link_to` in [the Rails API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to). – Alexander Azarov Dec 14 '18 at 21:05
  • Another example is how `` works: [API doc](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html) – Alexander Azarov Dec 15 '18 at 06:16
2

When using capture from your controller the output is appended to the page buffer, as a result the <%= from of your erb is outputting immediately to the page output.

To work around, you need to use <% instead within your test_h2 block. So to get the expected behavior in both cases, use this syntax:

<%= test_h1 do %>
  <%= 'test1' %>
<% end -%>

<%= test_h2 do %>
  <% 'test2' %>
<% end -%>

More info in this article: https://thepugautomatic.com/2013/06/helpers/

Benj
  • 12,503
  • 1
  • 34
  • 69
  • (a) I might have `...` and in this case `` will be outside of ``; (b) what is the logic behind this? Why is it working in this way for controller helper methods? Why it's working as expected for helper modules? – Alexander Azarov Dec 17 '18 at 20:02
  • `capture` is pushing the "captured' content to the page buffer right away. So everything which is "captured" AND "outputted" (such as raw html, or content within ` tag. – Benj Dec 18 '18 at 08:02
2

capture overrides current output buffer and just calls the block (which is still bound to other view context), thus override has no effect when called from controller because view_context is not the same context the view is being rendered in.

To work around contexts you can define your helper like so:

# in controller
helper do
  def test_h3(&block)
    # this will run in view context, so call `controller.some_func` to access controller instance
    link_to '/url' do
      capture(&block)
    end
  end
end
Vasfed
  • 16,687
  • 10
  • 43
  • 47
  • Thank you for the explanation and the suggestion on how to define a helper method. The public API seems to be `helper do def test_h3...; end` and it is described in the API doc for `helper` (internally it does exactly that `_helpers.module_eval`) – Alexander Azarov Dec 21 '18 at 19:22
-1

The idomatic way to do it in rails would be to move the test_h2 method to a concern and include that concern in controller as well as helper class.

Or else define test_h2 as helper_method in your controller class.

But generally methods that are needed in multiple places should be placed in concerns, and include those concerns wherever needed.

Also if you need methods for views, then include concerns or define your own methods inside helpers.

Refer Can we call a Controller's method from a view (as we call from helper ideally)?
How to use concerns in Rails 4

shirish
  • 673
  • 4
  • 8
  • Use helper_method :test_h2 in your controller as a workaround. But the idiomatic way would be concerns as far as I know. – shirish Dec 16 '18 at 15:28