Search code examples
ruby-on-railscocoon-gem

Rails cocoon: Nested fields disappear after I submit the form


I have the following three models and associations:

class ParentProduct < ApplicationRecord
  has_many :child_products
  accepts_nested_attributes_for :child_products, allow_destroy: true
  has_many :attachments, dependent: :destroy
  accepts_nested_attributes_for :attachments, allow_destroy: true
end

class ChildProduct < ApplicationRecord
  belongs_to :parent_product
  has_many :attachments, dependent: :destroy
  accepts_nested_attributes_for :attachments, allow_destroy: true
end

class Attachment < ApplicationRecord
 belongs_to :parent_product, optional: true
 belongs_to :child_product, optional: true
 mount_uploader :image, AttachmentUploader
end

This in the controller:

def new
  @parent_product = ParentProduct.new
  8.times{ @parent_product.attachments.build }
end

def create
  @parent_product = ParentProduct.new(product_params)
  respond_to do |format|
    if @parent_product.save
      flash[:success] = "Product was successfully added."
      format.html { redirect_to product_index_path }
      format.json { render :new, status: :created, location: @parent_product }
    else
      format.html { render :new }
      format.json { render json: @parent_product.errors, status: :unprocessable_entity }
    end
  end
end

def product_params
  params.require(:parent_product).permit(:name, :price, :color, :size, :description, :subcategory_id,
                                       attachments_attributes: [:id, :image, :parent_product_id, :child_product_id],
                                       child_products_attributes: [:price, :color, :size, :parent_product_id)
end

I use the following form in order to create a parent product and the attachments for this parent product. Also with the same form I can create many child products(along with the attachments for each child product) and associate them with this parent product:

<%= simple_form_for @parent_product, url: parent_product_path, method: :post, validate: true do |f| %>

  <%= f.simple_fields_for :attachments do |attachment| %>
    <%= render 'parent_products/attachment_fields', form: attachment  %>
  <% end %>

  <%= f.simple_fields_for :child_products do |child_product| %>
    <%= render 'parent_products/child_product_fields', form: child_product %>
  <% end %>

  <div class="links float-right" style="margin-bottom: 30px;">
    <%= link_to_add_association raw('<i class="far fa-plus-square"></i> Add a product variation'), 
    f, :child_products, form_name: 'form',
    wrap_object: Proc.new {|child| 8.times {child.attachments.build }; child }, 
    style: "color: grey; font-size: 14px;" %>
  </div>
<% end %>

As you can see above, I'm using this wrap_object: Proc.new {|child| 8.times {child.attachments.build }; child } for the child_products nested form, in order to create 8 attachment fields when I click and add that nested form.

Inside the child_products nested form I have another nested form in order to create the attachments for the child product.

<%= form.simple_fields_for :attachments do |attachment| %>
  <%= render 'parent_products/attachment_fields', form: attachment  %>
<% end %>

The partial for the attachment_fields has only this input:

<%= form.input :image, label: false, as: :file, input_html: %>

The only issue is that when I submit the form and for some reason(validation error) the form gets rendered again, the 8 attachment fields disappear. Any idea on how can I fix this issue?


Solution

  • I now see the problem! The attachments for your child_products are not permitted.

    In your product_params you now have:

    def product_params
      params.require(:parent_product).permit(:name, :price, :color, :size, :description, :subcategory_id,
                                           attachments_attributes: [:id, :image, :parent_product_id, :child_product_id],
                                           child_products_attributes: [:price, :color, :size, :parent_product_id)
    end
    

    and it should be (reformatted for clarity)

    def product_params
      params.require(:parent_product).permit(
          :name, :price, :color, :size, :description, :subcategory_id,
          attachments_attributes: [:id, :image, :parent_product_id, :child_product_id],
          child_products_attributes: [
               :price, :color, :size, :parent_product_id, 
               attachments_attributes: [:id, :image, :parent_product_id, :child_product_id]
          ])
    end
    

    You should also see a warning in your development.log that the parameter(s) are blocked.