2

I am using a theme that has very specific layouts and I wanted to make a failsafe way to you rails forms.

I have a layout app/views/shared/forms/fields/_layout.html.erb

<div class="js-form-message mb-4">
  <div class="js-focus-state input-group u-form">
    <div class="input-group g-brd-primary--focus">
      <%= yield(:field) %>
    </div>
  </div>
</div>

And I have two partials. 1-st partial: app/views/shared/forms/fields/_email.html.erb

<% form = locals[:form] %>
<% locals[:required] = locals[:required].nil? ? true : locals[:required] %>
<% locals[:placeholder] = locals[:placeholder] || t('forms.shared.email.placeholder') %>


<%= render layout: "shared/forms/fields/layout", locals: locals do %>
  <% content_for(:field) do %>
    <%= form.email_field :email, 
      placeholder: locals[:placeholder], 
      class: "form-control g-py-15 g-px-15",
      "data-error-class"=>"u-has-error-v1-3",
      "data-success-class"=>"u-has-success-v1-2",
      "data-msg-email" => t('forms.shared.email.validate'),
      "data-msg" => t('forms.shared.required'),
      autofocus: locals[:autofocus],
      required: locals[:required] %>  
  <% end %>
<% end %>

2-nd partial: app/views/shared/forms/fields/_login.html.erb

<% form = locals[:form] %>
<% locals[:required] = locals[:required].nil? ? true : locals[:required] %>
<% locals[:placeholder] = locals[:placeholder] || t('forms.shared.login.placeholder') %>


<%= render layout: "shared/forms/fields/layout", locals: locals do %>
  <% content_for(:field) do %>
    <%= form.email_field :login, 
      placeholder: locals[:placeholder], 
      class: "form-control g-py-15 g-px-15",
      "data-error-class"=>"u-has-error-v1-3",
      "data-success-class"=>"u-has-success-v1-2",
      "data-msg" => t('forms.shared.required'),
      autofocus: locals[:autofocus],
      required: locals[:required] %>  
  <% end %>
<% end %>

And when I do this:

<%= render "shared/forms/fields/email", locals: {form: f} %>

<%= render "shared/forms/fields/login", locals: {form: f} %>

I get

Email Field

Email Field/Login Field

I found out that content_for 'appends' the block that you give it and then when I yield the whole block is returned.

The first time there is nothing in content_for(:field) and it appends to it Email Field. But the second time it does not clear its content and just appends Login Field to it.

I am thinking of adding additional complexity to layout.html.erb so just keeping it inline isn't an option.

Is there a way to tell to the layout only to yield the 'newest' value of content_for.

EDIT:

I wrote a method to flush after an yield, suggesting that the same key would be used again:

def yield_and_flush!(content_key)
  view_flow.content.delete(content_key)
end

2 Answers2

2

content_for has flush option to reset previous content:

<% content_for :field, flush: true do %>
   new content here
<% end %>
Vasfed
  • 18,013
  • 10
  • 47
  • 53
  • This does not work, because I added an :after_field. Which means that if the first flushes everything before it and then the second does not have :after_field content. Then the content from the first is going to be rendered. I need something that would flush after the content is rendered. – Mihail hidr0 Kirilov Nov 12 '19 at 15:41
  • 1
    @Mihailhidr0Kirilov you can render manually using `<%= content_for(field_name) %>` and then reset with `flush` – Vasfed Nov 12 '19 at 15:51
  • This is what I did but with the yield_and_flush! method. @Vasfed – Mihail hidr0 Kirilov Nov 13 '19 at 11:08
0

The solution was this to write an yield_and_flush! method. I saw the solution here

def yield_and_flush!(content_key)
  view_flow.content.delete(content_key)
end
  • 1
    Using rails internal apis, that are not mentioned in rails' guides is not a good practice, because this can be broken in any rails release, even minor/patch, without any deprecation warnings etc. – Vasfed Nov 12 '19 at 15:54
  • I agree. Do you have another solution? I wrote tests on the behaviour and I hope that, that is going to keep me in check in the long run. – Mihail hidr0 Kirilov Nov 13 '19 at 11:07
  • You can use public api from my answer here - `content_for(content_key).tap{ content_for(content_key, '', flush: true) }` – Vasfed Nov 13 '19 at 11:12