ruby-on-railsruby-on-rails-5form-forcocoon-gem

Automatically adding nested child partials on link_to_add_association click using Cocoon


I have a form with 4 nested levels that I'm using to dynamically add items, shipments, and calculate costs. I have the form set up to where I can add/remove everything I need with button clicks. Everything about the initial render of the page is as expected.

However, when I go to add new instances of the mid-level nests (the SubQuote and QuoteShipment), it only renders itself (e.g. SubQuote) and not any of the partials referenced in its partial (QuoteShipment). I want a button click to render all of the nested partials beneath of it automatically.

My relevant models are structured:

class Quote < ApplicationRecord
  has_many :sub_quotes, dependent: :destroy, inverse_of: :quote
  accepts_nested_attributes_for :sub_quotes, reject_if: :all_blank
end


class SubQuote < ApplicationRecord
    belongs_to :quote
    has_many :quote_shipments, dependent: :destroy, inverse_of: :sub_quote
    accepts_nested_attributes_for :quote_shipments, reject_if: :all_blank
end

class QuoteShipment < ApplicationRecord
    has_many :quote_items, dependent: :destroy, inverse_of: :quote_shipment, class_name: "::QuoteItem"
    belongs_to :sub_quote
    accepts_nested_attributes_for :quote_items, reject_if: :all_blank
end

Class QuoteItem < ApplicationRecord
    belongs_to :quote_shipment
end

Partial for SubQuote class that renders the QuoteShipment partial on page load, but not on link_to_add_association click.

<div class="col nested-sub_quote-fields" id="sub_quote_identifier">

  <div class="row">
    <%= link_to_remove_association button_tag('remove sub_quote', type: "button", class: "btn btn-sm btn-outline red-btn add-quote"), f , { wrapper_class: "nested-sub_quote-fields" }%>
  </div>

  <div class="row" class="">
  
    <div id="sub_quote" class="col mt-0 mt-md-4 mb-4 p-0">
      <div class="col ">
        <div class="col-lg col-sm-12 col-md-12">
          <%= f.fields_for :quote_shipments do |shipment| %>
            <%= render partial: "quotes/quote_shipment_fields", :locals => { :f => shipment } %>
          <% end %>
          <div class='links'>
            <%= link_to_add_association button_tag('Add Shipment', type: "button", class: "btn btn-sm btn-outline btn-primary add-quote"), f, :quote_shipments %>
          </div>
        </div>
      </div>
    </div>
    
    <div class="col-lg-5 col-sm-12 col-md-12">
      <%= render partial: "quotes/cost", locals: { f: f } %>
    </div>
    
  </div>
</div>

For example with the above relationships:

I've tried:

I think in a worse case scenario, I could generate different partials for each different level's link_to_add_association button that I have, but that seems very redundant


Solution

  • The link_to_add_association has the :wrap_object option (check documentation) that allows to add extra initialisation before rendering the partial.

    So in your case you could so something like:

    = link_to_add_association button_tag('Add Shipment', type: "button", class: "btn btn-sm btn-outline btn-primary add-quote"), 
                              f, :quote_shipments, 
                              wrap_object: Proc.new{|shipment| shipment.quote_items.build; shipment }
    

    Inside the wrap_object partial you could do any initialisation you want, as long as you return the to be rendered nested item (in this example shipment). And in this example we build a QuoteItem so it will be rendered.

    For the SubQuote you could so something similar. E.g. something like

    wrap_object: Proc.new{|sub_quote| shipment = sub_quote.quote_shipments.build; shipment.quote_items.build; sub_quote }