Question

I have the following models:

class Individual < ActiveRecord::Base
  has_many visits
  has_many cities, :through => visits
  accepts_nested_attributes_for :visits, reject_if: proc { |a| a[:city_id].blank? }, allow_destroy: true
end

class Visit < ActiveRecord::Base
  belongs_to individual
  belongs_to city
end

class City < ActiveRecord::Base
  has_many visits
  has_many individuals, :through => visits
end

I'm trying to develop a form for each individual that lists all the cities in my DB and allows them to tick which city they've visited and then add some basic attributes about that city. What I currently have is:

             <%= form_for(individual) do |f| %>
                  <% for city in cities.each do %>
                    <tr>
                      <td>
                          <%= check_box_tag "individual[visits_attributes][][city_id]", city_id.id, individual.cities.include?(city) %>
                      </td>
                      <td>
                        <label>How good was your visit here?</label>
                        <%= select_tag "individual[visits_attributes][][sentiment]", options_for_select([ ...... ])) %>
                      </td>
                      <td>
                        <label>What was the weather like?</label>
                        <%= select_tag "individual[visits_attributes][][weather]", options_for_select([ ...... ])) %>
                      </td>
                      <td>
                        <label>Where did you stay?</label>
                        <%= select_tag "individual[visits_attributes][][accomodation]", options_for_select([ ...... ])) %>
                      </td>
                    </tr>
                  <% end %>
                  <%= f.submit "Update", :class => "btn" %>
              <% end %>

This is kind-of working, but it just feels wrong and un-rails like. Also, it creates duplicate records and I haven't been able to get it to delete a record yet.

Can anyone suggest a better way of doing this? Or help me refactor what I already have?

Was it helpful?

Solution 2

This is how I eventually solved it.

Incorporating @carlos's input I used Ryan Bates's helper for dynamically adding new nested_attributes fields but altered it to work in my list of cities

<%= form_for(individual) do |f| %>
  <% for city in cities.each do %>
    <% if individual.visits.include?(city) %>
      <% visit = f.object.visits.find_by_city_id(city.id) %>
      <%= f.fields_for(:visits, visit, child_index: visit.id ) do |existing_visit| %>
        <tr>
          <td>
              <%= check_box_tag "individual[visits_attributes][#{visit.id}][city_id]", city.id, individual.visits.include?(city) %>
              <%= existing_visit.hidden_field :_destroy %>
          </td>
          <td>
            <label>How good was your visit here?</label>
            <%= existing_visit.select :sentiment, options_for_select([ ...... ])) %>
          </td>
          <td>
            <label>What was the weather like?</label>
            <%= existing_visit.select :weather, options_for_select([ ...... ])) %>
          </td>
          <td>
            <label>Where did you stay?</label>
            <%= existing_visit.select :accomodation, options_for_select([ ...... ])) %>
          </td>
        </tr>
      <% end %>
    <% else %>
      <% visit = f.object.visits.build %>
      <% random_index = city.id.to_s+"_"+Time.now.to_i.to_s %>
      <%= f.fields_for(:visits, visit, child_index: random_index) do |new_visit| %>
        <tr>
          <td>
              <%= check_box_tag "individual[visits_attributes][#{random_index}][city_id]", city.id, nil %>
          </td>
          <td>
            <label>How good was your visit here?</label>
            <%= new_visit.select :sentiment, options_for_select([ ...... ])) %>
          </td>
          <td>
            <label>What was the weather like?</label>
            <%= new_visit.select :weather, options_for_select([ ...... ])) %>
          </td>
          <td>
            <label>Where did you stay?</label>
            <%= new_visit.select :accomodation, options_for_select([ ...... ])) %>
          </td>
        </tr>
      <% end %>
    <% end %>
    <%= f.submit "Update", :class => "btn" %>
  <% end %>
<% end %>

The key was in using an if statement to check whether the record was a new record or existing and then correctly setting the child_index: on the fields_for helper.

I also had to use the check_box_tag rather than .check_box helper so that it didn't produce the hidden check box field.

I use Javascript to trigger the hidden field to delete a record when the user unchecks the box next to the city that the visit took place.

Get in touch if you need more details.

OTHER TIPS

I don't have time to write a full answer, but the gist is that you want to use fields_for to output association fields against the object. This would also be referred to in Rails as a 'Nested Model Form'.

Check out these resources for some help:
Nested Model Form Railscast
Rails API fields_for

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top