Search code examples
ruby-on-railssimple-formcocoon-gemwicked-gem

Cocoon has_one association breaking upon save/update using Wicked Gem - Rails


I've seen some similar questions to this but nothing relating to using the Cocoon gem with the Wicked multi step form Gem in with Rails. The form saves fine when I have a has_many association but when there is a has_one association (Client has_one doctor) the record seems to save and then delete. Spent so long trying to figure this out but just can't see why it's happening.

class QuotesController < ApplicationController
include Wicked::Wizard
before_action :set_client, only: [:show, :update, :quote_success]
steps :profile, :employment, :general_questions, :indemnity_details, :declarations

def show
    @client.build_doctor
    @client.old_insurers.build
    @client.practice_addresses.build
    render_wizard
end

def update
    @client.update(client_params)
    render_wizard @client
end

def quote_success; end

private

def set_client
    current_user = Client.find_by_id(session[:current_user_id])
    @client = current_user
end

def finish_wizard_path
    if @client.valid?
        ClientMailer.new_quote(@client).deliver
        ClientMailer.new_quote_user_message(@client).deliver
      end
        quote_success_path
    end
end

def client_params
    params.require(:client).permit(:name, :email, :email_confirmation, :phone, :date_required,
                                   :title, :first_name, :last_name, :date_of_birth, :nationality, :reg_body, 
                                   practice_addresses_attributes: [:id, :pa_line1, :pa_line2, :pa_line3, :pa_town, :pa_county, :pa_postcode, :other_practices, :_destroy],
                                   old_insurers_attributes: [:id, :insurer, :oi_to, :oi_from, :oi_limit, :oi_excess, :oi_premium, :_destroy],
                                   doctor_attributes: [:id, :gp_locum, :gp_locum_sessions, :gp_locum_locations])
     end
  end

Client Model:

class Client < ActiveRecord::Base
has_one :doctor, dependent: :destroy
has_many :practice_addresses, dependent: :destroy
has_many :callbks, inverse_of: :client
has_many :old_insurers, dependent: :destroy
accepts_nested_attributes_for :doctor
accepts_nested_attributes_for :old_insurers, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :callbks
accepts_nested_attributes_for :practice_addresses, reject_if: :all_blank, allow_destroy: true
end

Doctor Model:

class Doctor < ActiveRecord::Base
    belongs_to :client
end

View part in question:

 <%= simple_form_for @client, url: wizard_path do |f| %>

 <%= yield f %>
 ....

<div id="doctors">
    <%= f.simple_fields_for :doctor do |gp| %>
    <%= render 'doctor_fields', f: gp %>
    <% end %>
</div>

Doctor_fields:

<div class="nested-fields">
<div class="panel panel-default">
    <div class="panel-heading">
        <strong>General Questions - GP</strong>
    </div>
    <div class="panel-body">

        <div class="table-responsive">
            <div class=bs-example data-example-id=striped-table>
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <th>Are you a:</th>
                            <th>Yes / No</th>
                            <th>Number of Sessions per week</th>
                            <th>Number of Locations</th>
                        </tr>
                        <tr>
                            <td>Locum</td>
                            <td><%= f.input :gp_locum, as: :radio_buttons, checked: false, label: false %></td>
                            <td><%= f.input :gp_locum_sessions, label: false, required: false %></td>
                            <td><%= f.input :gp_locum_locations, label: false, required: false %></td>
                        </tr>
            </div>
    </table>
</div>
</div>

Log:

Started GET "/quotes/employment" for ::1 at 2017-03-13 10:27:07 -0300
Processing by QuotesController#show as HTML
  Parameters: {"id"=>"employment"}
  Client Load (1.6ms)  SELECT  "clients".* FROM "clients" WHERE         "clients"."id" = $1 LIMIT 1  [["id", 47]]
  Doctor Load (1.1ms)  SELECT  "doctors".* FROM "doctors" WHERE "doctors"."client_id" = $1 LIMIT 1  [["client_id", 47]]
  Rendered layouts/_quote_page_start.html.erb (2.0ms)
  Rendered quotes/_doctor_fields.html.erb (234.4ms)
  Rendered quotes/_form.html.erb (364.0ms)
  Rendered layouts/_quote_page_mobile.html.erb (0.2ms)
  Rendered quotes/employment.html.erb within layouts/application (423.2ms)
  Rendered layouts/_shim.html.erb (0.1ms)
  Profession Load (1.7ms)  SELECT "professions".* FROM "professions"  ORDER BY name ASC
  Rendered layouts/_header.html.erb (8.6ms)
  Rendered layouts/_footer.html.erb (11.4ms)
Completed 200 OK in 1920ms (Views: 1845.1ms | ActiveRecord: 13.7ms)


Started PATCH "/quotes/employment" for ::1 at 2017-03-13 10:27:21 -0300
Processing by QuotesController#update as HTML
  Parameters: {"utf8"=>"✓",     "authenticity_token"=>"7xvqW6SpbBgKy62zfdPclexYD9cxqNR/F8p+twW87F/vSz/5kmpRMCE59    LcL4taIclWj4ZeVQ6CRasqqy38iNw==", "client"=>{"profession_area"=>"General     Practicioner", "profession_area_other"=>"das", "doctor_attributes"=>    {"gp_locum"=>"false", "gp_locum_sessions"=>"212", "gp_locum_locations"=>"21"},     "commit"=>"Next", "id"=>"employment"}
  Client Load (2.0ms)  SELECT  "clients".* FROM "clients" WHERE "clients"."id" = $1 LIMIT 1  [["id", 47]]
   (0.2ms)  BEGIN
      Doctor Load (0.5ms)  SELECT  "doctors".* FROM "doctors" WHERE     "doctors"."client_id" = $1 LIMIT 1  [["client_id", 47]]
  SQL (0.6ms)  INSERT INTO "doctors" ("gp_locum", "gp_locum_sessions", "gp_locum_locations", "client_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35) RETURNING "id"  [["gp_locum", "f"], ["gp_locum_sessions", 212], ["gp_locum_locations", 21], ["client_id", 47], ["created_at", "2017-03-13 13:27:21.111656"], ["updated_at", "2017-03-13 13:27:21.111656"]]
   (0.7ms)  COMMIT
  (0.7ms)  BEGIN
   (0.4ms)  COMMIT
Redirected to http://localhost:3000/quotes/general_questions
Completed 302 Found in 38ms (ActiveRecord: 5.1ms)


Started GET "/quotes/general_questions" for ::1 at 2017-03-13 10:27:21 -0300
Processing by QuotesController#show as HTML
  Parameters: {"id"=>"general_questions"}
  Client Load (0.9ms)  SELECT  "clients".* FROM "clients" WHERE "clients"."id" = $1 LIMIT 1  [["id", 47]]
  Doctor Load (0.4ms)  SELECT  "doctors".* FROM "doctors" WHERE "doctors"."client_id" = $1 LIMIT 1  [["client_id", 47]]
   (0.1ms)  BEGIN
  SQL (0.8ms)  DELETE FROM "doctors" WHERE "doctors"."id" = $1  [["id", 29]]
   (0.5ms)  COMMIT

Any help would be great thanks!! Please let me know if you need anything else.


Solution

  • [NOTE: general remark about link_to_add_association and has_one]

    The link_to_add_association will try to build a new item on the association. If you have a has_one relation, this effectively means it will replace the existing, if there is one.

    If you really need to link_to_add_association you can try the following:

    = link_to_add_association 'add doctor', @form_obj, :doctor, force_non_association_create: true
    

    (this will build a new doctor, but not using the association, this might mean some defaults defined on the association are not copied, BUT the existing doctor will not be overwritten unless chosen so after save of the form)

    [UPDATE: in your case]

    Just remove the @client.build_doctor from the show method. This re-initialises the doctor every time it is called. I assume you would only want this in the new method.

    Or, if I seem to read correctly, you only have a show and no edit/new do something like

    @client.build_doctor unless @client.doctor.present?