ruby-on-railsruby-on-rails-5cocoon-gem

has_many through and values on the join table with Cocoon


I can't seem to get my forms working correctly despite following many guides, tutorials and examples.

I have a model location.rb:

class Location < ActiveRecord::Base
  has_many :location_gateways
  has_many :gateways, through: :location_gateways, dependent: :destroy 
  accepts_nested_attributes_for :location_gateways, reject_if: :all_blank, allow_destroy: true
end

The joining table has an attribute bearing and the model (location_gateway.rb):

class LocationGateway < ActiveRecord::Base
  belongs_to :location
  belongs_to :gateway, dependent: :destroy
  accepts_nested_attributes_for :gateway, reject_if: :all_blank, allow_destroy: true
end

The form looks like this (location/new.html.erb):

<div id="locations-form">
        <%= form_with model: @location do |f| %>
          <%= render 'partials/locations_form', f:f %>

          <div class="form-group" id="location_gateways"> 
            <b><%= 'Add gateway', f, :location_gateways, partial: 'partials/gateway_fields'%></b>
          </div>

          <div class="actions">
            <%= f.submit nil, :class => "btn btn-primary" %>
          </div>
        <% end %>
</div>

Nested form (_gateway_fields.html.erb)

<div class="nested-fields" >
    <div class="row">
        <%= f.fields_for :gateways do |g| %>
          <div class="col-md-4" >
            <%= g.text_field :longitude, class: 'form-control' %>
          </div>
          <div class="col-md-4">
            <%= g.text_field :latitude, class: 'form-control' %>
          </div>
        <% end %>
        <div class="col-md-2" >
          <%= f.text_field :bearing, class: 'form-control gateway-longitude' %>
        </div>
        <div class="col-md-2">
          <%= link_to_remove_association  raw(icon_trash), f  %>
        </div>
    </div>
  </div>
</div>

I'd like to add latitude and longitude for the gateway association while adding the bearing to the joining table location_gateway.

However, my params comes out like this:

{ ... "location_gateways_attributes"=>
  {"1593162006925"=>
    {
       "gateways"=>{"longitude"=>"12.3456", "latitude"=>"13.4567"}, 
       "bearing"=>"123", "_destroy"=>"false"
    }
  }
}

Why does it become gateways rather than gateways_attributes?


Solution

  • I initially misread your code: I thought you created the gateways directly on the has_many :gateways association. In which case you would be missing the accepts_nested_attributes_for :gateways on Location level.

    But after your feedback and looking a bit closer I noticed you correctly followed the associations (you have a double/nested fields_for), so you have the correct accepts_nested_attributes_for declared in your models.

    However.... in your nested model you write fields_for :gateways --- BUT since you are now nested in a LocationGateway that association does not exist there, instead you have to write f.fields_for :gateway and then it will all work as expected.

    So what I said before still stands: That is also why the parameter is called gateways instead of gateways_attributes: that is indicative of a missing accepts_nested_attributes_for. Or more precisely in this case: using the wrong association.

    [EDIT] Now we have an link_to_add_association 'Add gateway', f, :location_gateways, partial: 'partials/gateway_fields' which creates a new LocationGateway, but not the nested Gateway. So now you have two options:

    and for this last option we can use the cocoon option wrap_object as follows:

    link_to_add_association 'Add gateway', f, :location_gateways, 
       partial: 'partials/gateway_fields',
       wrap_object: Proc.new {|x| x.build_gateway; x }