maki
maki

Reputation: 535

rails fields_for nested objects

I have an CEvemt model which has an association to Schedule model, now I'm trying to implement a form with multiple schedule objects: an user can add more schedule fields if he clicks the "Add more" link.

My form code:

<% 3.times do |x| %>
  <%= render :partial => "schedule_field", :locals => {:event => @schedule} %>
<% end %>
<%= hidden_field_tag :schedule_fields_count, 2 %>
<%= link_to "Add more", "#", :id => "add_schedule_field" %>

partial:

<%= fields_for :event, event.schedules.build do |s| %>
  <div class="row">
    <div class="small-3.5 columns">
        <%= s.datetime_select :start %>      
    </div>
    <div class="small-3.5 columns">     
        <%= s.datetime_select :finish %>
    </div>
    <div class="small-5 columns">
      <%= s.text_field :description %>
    </div>
  </div>
<% end %>

and for load more I use this view

$("#schedule_fields").
  append("<%= escape_javascript(render :partial => 'schedule_field', :locals => {:event => @event}) %>");

$("#schedule_fields_count").val("<%= @number %>");

The problem is that this code doesn't render what I want.. For example on form I have 3 schedule description fields which all have the same name schedule[description], so the date is incorrect and I can't save it.

Upvotes: 1

Views: 2193

Answers (1)

BroiSatse
BroiSatse

Reputation: 44715

Firstly, you need following line in your model:

accepts_nested_attributes_for :schedules

This line will define a setter schedules_attributes= which will be later used in your form. You might want to pass some extra options like allow_destroy: true or reject_if: :all_blank.

Following lines:

<% 3.times do |x| %>
  <%= render :partial => "schedule_field", :locals => {:event => @schedule} %>
<% end %>

with partial are incorrect. You need to call field_for only once, and it should be called on your current form builder object. Instead do:

<%= f.fields_for :schedules do |s| %>
  <%= render 'schedule_fields', f: s %>
  <%= link_to "Add more", "#", :id => "add_schedule_field" %>
<% end %>

fields_for will check whether object associated with form_builder implements schedules_attributes= setter (created by accepts_nested_attributes_for method) and will behave accordingly.
Note: you do not need to specify partial: in render - this is a default inside a view. I would rename your partial to schedule_fields to use it with cocoon, but I'll get to it later. Your partial should only contains fields without fields_for call:

<div class="row">
  <div class="small-3.5 columns">
    <%= f.datetime_select :start %>      
  </div>
  <div class="small-3.5 columns">     
    <%= f.datetime_select :finish %>
  </div>
  <div class="small-5 columns">
    <%= f.text_field :description %>
  </div>
</div>

Building new objects should be placed in your controller, not in your view (as otherwise you won't be able to use the same partial for editing existing schedules). So in your controller do sth like:

(3 - @user.schedules.count).times { @user.schedules.build }

This will ensure you always have at least 3 schedules to display.

This should do it. As a lest step I would advise you to look at mentioned above coccon gem. It is quite powerful tool for client side association creation and deletion. You can read about it here: https://github.com/nathanvda/cocoon

Upvotes: 1

Related Questions