Search code examples
ruby-on-railsassociationscocoon-gemaccepts-nested-attributes

Rails Nested Fields With Cocoon - link_to_add_association is Deleting Records


I'm using nested fields and accept nested attributes with a 3 level nested relationship. The 3rd level (I don't think it matters that it's third level) relationship gets deleted when editing the parent.

Setup has one material, material has one rotap_analysis, rotap_analysis has many rotap_sieves. The following code is what triggers the delete ONLY WHEN EDITING AN EXISTING SETUP THAT ALREADY HAS A ROTAP ANALYSIS, creating new or editing and creating new rotap analysis works fine:

  <%= f.fields_for :rotap_analysis do |ra| %>
    <%= render 'materials/rotap_analysis_fields', f: ra %>
  <% end %> 
  <div class="links float-e-margins">
  <!-- this is deleting rotap analysis -->
    <%= link_to_add_association '+ rotap analysis', f, :rotap_analysis, partial: 'materials/rotap_analysis_fields', class: "btn btn-info btn-xs" %>
  </div>

Here is the server log with the delete transactions upon rendering with "fields_for" helper.

RotapAnalysis Load (0.3ms)  SELECT "rotap_analyses".* FROM "rotap_analyses" WHERE "rotap_analyses"."material_id" = $1 LIMIT $2  [["material_id", 23], ["LIMIT", 1]]
  ↳ app/views/materials/_material_fields.html.erb:77
  RotapSieve Load (4.2ms)  SELECT "rotap_sieves".* FROM "rotap_sieves" WHERE "rotap_sieves"."rotap_analysis_id" = $1  [["rotap_analysis_id", 18]]
  ↳ app/views/materials/_rotap_analysis_fields.html.erb:24
  Rendered materials/_rotap_sieve_fields.html.erb (Duration: 1.0ms | Allocations: 488)
  Rendered materials/_rotap_sieve_fields.html.erb (Duration: 0.9ms | Allocations: 475)
  Rendered materials/_rotap_analysis_fields.html.erb (Duration: 13.0ms | Allocations: 2823)
  TRANSACTION (0.3ms)  BEGIN
  ↳ app/views/materials/_material_fields.html.erb:82
  RotapSieve Destroy (0.4ms)  DELETE FROM "rotap_sieves" WHERE "rotap_sieves"."id" = $1  [["id", 17]]
  ↳ app/views/materials/_material_fields.html.erb:82
  RotapAnalysis Destroy (0.5ms)  DELETE FROM "rotap_analyses" WHERE "rotap_analyses"."id" = $1  [["id", 18]]
  ↳ app/views/materials/_material_fields.html.erb:82
  TRANSACTION (1.0ms)  COMMIT
  ↳ app/views/materials/_material_fields.html.erb:82
  Rendered materials/_rotap_sieve_fields.html.erb (Duration: 0.9ms | Allocations: 475)
  Rendered materials/_rotap_analysis_fields.html.erb (Duration: 4.2ms | Allocations: 1495)
  Rendered materials/_material_fields.html.erb (Duration: 43.9ms | Allocations: 12427)
  Rendered setups/_form.html.erb (Duration: 123.8ms | Allocations: 42184)
  Rendered setups/edit.html.erb within layouts/application (Duration: 124.1ms | Allocations: 42245

I have several nested attributes throughout the application and I have never ran into this issue. What the heck is going on?


Solution

  • As documented this is weird but well known behaviour when using a has_one relationship. By default cocoon uses the association to create the new nested item. But then rails assumes, if you already have one, creating a new one will replace it (which does make sense).

    However: when using the link_to_add_association we pre-create an empty item to fill, so it will always delete it.

    There is a simple workaround: for has_one associations you can use the force_non_association_create: true which will not create the child element using the association, and thus will not remove existing items (for has_one associations).

    So in your case you would write:

    <%= link_to_add_association '+ rotap analysis', f, :rotap_analysis, 
           partial: 'materials/rotap_analysis_fields', 
           force_non_association_create: true, 
           class: "btn btn-info btn-xs" %>