Simple_form: Simple_form in Rails 6 can't render nested attributes

Created on 11 Aug 2020  路  2Comments  路  Source: heartcombo/simple_form

See issue https://github.com/heartcombo/simple_form/issues/1699

https://github.com/Vinnie1991/simple-forms_nesting

The models are

class Event < ApplicationRecord
  has_many :time_records
  accepts_nested_attributes_for :time_records
end
class TimeRecord < ApplicationRecord
  belongs_to :event
end

Form is:

<%= simple_form_for(@event) do |f| %>
  <%= f.error_notification %>
  <%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>

  <div class="form-inputs">
    <%= f.input :title %>

    <%= f.simple_fields_for :time_records do |form| %>

      <%= form.date_field :from_date %>
      <%= form.date_field :till_date %>

    <% end %>
  </div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

When I use accepted_nested_params, the two datefields disappear from the view.

Ruby 2.6.6
Rails 6.0.3.2
SimpleForms 5.0.2

_Originally posted by @Vinnie1991 in https://github.com/heartcombo/simple_form/issues/1699#issuecomment-666447295_

Most helpful comment

@Vinnie1991 @jeff-french so, when there's no accepts_nested_attributes_for, what Rails (and thus SimpleForm) is doing is generating a series of nested form fields. You happened to call it time_records, and so the form will be rendered like this:

<div聽class="form-inputs">
  <div聽class="input string optional event_title">
    <label聽class="string optional"聽for="event_title">Title</label>
    <input聽class="string optional"聽type="text"聽name="event[title]"聽id="event_title" />
  </div>
  <input聽type="date"聽name="event[time_records][from_date]"聽id="event_time_records_from_date" />
  <input聽type="date"聽name="event[time_records][till_date]"聽id="event_time_records_till_date" />
</div>

Note that nothing else happened there, other than scoping those "date" fields with an extra nested [time_records] name, which will result in a nested hash being submitted to the server:

"event"=>{"title"=>"Test", "time_records"=>{"from_date"=>"2020-10-07", "till_date"=>"2020-10-10"}}

In this scenario, time_records could named anything, like my_precious:

    <%= f.simple_fields_for :my_precious do |form| %>
      <%= form.date_field :from_date %>
      <%= form.date_field :till_date %>
    <% end %>

And you'd get similar output:

<input聽type="date"聽name="event[my_precious][from_date]"聽id="event_my_precious_from_date" />
<input聽type="date"聽name="event[my_precious][till_date]"聽id="event_my_precious_till_date" />

There's simply not link between that and the associated has_many time_records model. This can be confirmed by looking at the field names: your time_records table is using start and end column names, but the form is trying to render date fields with from_date and till_date.

Now, when you define accepts_nested_attributes_for, things change: https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/actionview/lib/action_view/helpers/form_helper.rb#L2165-L2167

Rails now will make that association between the model and the nested fields you're trying to render, and it should render one of those "fields for" blocks for each of the has_many associated objects you have.

So, the reason you see those fields "vanishing" when you add accepts_nested_attributes_for is simply because you have no associated object to the new (or existing) event you're rendering.

You can get the fields to render by either doing something like this in the controller: @event.time_records.build, or passing a second parameter to the fields_for call with the object(s) you want to render:

  def new
    @event = Event.new
    @event.time_records.build
  end

# or

    <%= f.simple_fields_for :time_records, TimeRecord.new do |form| %>
      <%= form.date_field :start %>
      <%= form.date_field :end %>
    <% end %>

Note that I have fixed the field names above to start and end, and that you can "build" or "pass" as many objects as you want there, and one group of fields will be generated for each, something like this:

<input聽type="date"聽name="event[time_records_attributes][0][start]"聽id="event_time_records_attributes_0_start" />
<input聽type="date"聽name="event[time_records_attributes][0][end]"聽id="event_time_records_attributes_0_end" />

I hope that helps, let me know if you run into any other issue getting SimpleForm to work.

All 2 comments

Seeing this exact same issue. Did you find any fix for it?

@Vinnie1991 @jeff-french so, when there's no accepts_nested_attributes_for, what Rails (and thus SimpleForm) is doing is generating a series of nested form fields. You happened to call it time_records, and so the form will be rendered like this:

<div聽class="form-inputs">
  <div聽class="input string optional event_title">
    <label聽class="string optional"聽for="event_title">Title</label>
    <input聽class="string optional"聽type="text"聽name="event[title]"聽id="event_title" />
  </div>
  <input聽type="date"聽name="event[time_records][from_date]"聽id="event_time_records_from_date" />
  <input聽type="date"聽name="event[time_records][till_date]"聽id="event_time_records_till_date" />
</div>

Note that nothing else happened there, other than scoping those "date" fields with an extra nested [time_records] name, which will result in a nested hash being submitted to the server:

"event"=>{"title"=>"Test", "time_records"=>{"from_date"=>"2020-10-07", "till_date"=>"2020-10-10"}}

In this scenario, time_records could named anything, like my_precious:

    <%= f.simple_fields_for :my_precious do |form| %>
      <%= form.date_field :from_date %>
      <%= form.date_field :till_date %>
    <% end %>

And you'd get similar output:

<input聽type="date"聽name="event[my_precious][from_date]"聽id="event_my_precious_from_date" />
<input聽type="date"聽name="event[my_precious][till_date]"聽id="event_my_precious_till_date" />

There's simply not link between that and the associated has_many time_records model. This can be confirmed by looking at the field names: your time_records table is using start and end column names, but the form is trying to render date fields with from_date and till_date.

Now, when you define accepts_nested_attributes_for, things change: https://github.com/rails/rails/blob/fe76a95b0d252a2d7c25e69498b720c96b243ea2/actionview/lib/action_view/helpers/form_helper.rb#L2165-L2167

Rails now will make that association between the model and the nested fields you're trying to render, and it should render one of those "fields for" blocks for each of the has_many associated objects you have.

So, the reason you see those fields "vanishing" when you add accepts_nested_attributes_for is simply because you have no associated object to the new (or existing) event you're rendering.

You can get the fields to render by either doing something like this in the controller: @event.time_records.build, or passing a second parameter to the fields_for call with the object(s) you want to render:

  def new
    @event = Event.new
    @event.time_records.build
  end

# or

    <%= f.simple_fields_for :time_records, TimeRecord.new do |form| %>
      <%= form.date_field :start %>
      <%= form.date_field :end %>
    <% end %>

Note that I have fixed the field names above to start and end, and that you can "build" or "pass" as many objects as you want there, and one group of fields will be generated for each, something like this:

<input聽type="date"聽name="event[time_records_attributes][0][start]"聽id="event_time_records_attributes_0_start" />
<input聽type="date"聽name="event[time_records_attributes][0][end]"聽id="event_time_records_attributes_0_end" />

I hope that helps, let me know if you run into any other issue getting SimpleForm to work.

Was this page helpful?
0 / 5 - 0 ratings