Discrepancy when capturing Rails view block
Asked Answered
V

4

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 ?

Vaules answered 14/12, 2018 at 10:43 Comment(0)
O
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
Obligor answered 21/12, 2018 at 12:16 Comment(1)
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)Vaules
D
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

Diverse answered 14/12, 2018 at 20:52 Comment(2)
Look at the example of using blocks inside link_to in the Rails API documentation.Vaules
Another example is how <%= form_for %> works: API docVaules
G
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/

Geranial answered 17/12, 2018 at 19:17 Comment(2)
(a) I might have <%= test_h2 do %><span>...</span><% end %> and in this case <span/> will be outside of <a></a>; (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?Vaules
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 <%= erb tags) will be rendered BEFORE your `test_h2̀ method is executed, and that's why you get 'test2' rendered before you <a> tag.Geranial
O
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
Obligor answered 21/12, 2018 at 12:16 Comment(1)
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)Vaules
A
-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

Assamese answered 16/12, 2018 at 14:27 Comment(1)
Use helper_method :test_h2 in your controller as a workaround. But the idiomatic way would be concerns as far as I know.Assamese

© 2022 - 2024 — McMap. All rights reserved.