ruby-on-railspostgresqldestroyruby-on-rails-6accepts-nested-attributes

Rails 6: Can't delete nested model. Random Insert statement


I using Rails 6 with Postgres and having issues deleting a nested model. A random insert statement gets generated after the association has been deleted.

Let me explain my set up.

Migrations

class CreateEntries < ActiveRecord::Migration[6.0]
  def change
    create_table :entries do |t|
      t.string :name
      t.timestamps
    end
  end
end

class Cards < ActiveRecord::Migration[6.0]
  def change
    create_table :cards do |t|
      t.string :card_number
      t.belongs_to :entry, null: true, foreign_key: true
      t.timestamps
    end
  end
end

Models

class Entry < ApplicationRecord
  has_one :card, dependent: :destroy
  accepts_nested_attributes_for :card, allow_destroy: true
end

class Card < ApplicationRecord
  belongs_to :entry
end

Controller

class EntriesController < ApplicationController
    before_action :set_entry

    def update
       @entry.update(entry_params)
    end

    def set_entry
       @entry = Entry.find(params[:id])
    end

    def entry_params
      params.require(:entry).permit(:name,
        card_attributes: [:id, :card_number, :_destroy]
      )
    end
end

Request Params

Parameters: {"authenticity_token"=>"CQ...Ucw==", "entry"=>{"card_attributes"=>{"_destroy"=>"true"}}, "id"=>"1"}

These are the logs

(0.2ms)  BEGIN

ConcessionCard Load (0.2ms)  SELECT "cards".* FROM "cards" WHERE "cards"."entry_id" = $1 LIMIT $2  [["entry_id", 1], ["LIMIT", 1]]

Card Destroy (0.4ms)  DELETE FROM "cards" WHERE "cards"."id" = $1  [["id", 2]]

Card Create (0.6ms)  INSERT INTO "cards" ("entry_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["entry_id", 1], ["created_at", "2019-09-06 13:50:41.100718"], ["updated_at", "2019-09-06 13:50:41.100718"]]

(0.3ms)  COMMIT

Why is insert being generated after the delete call? It's not even a rollback.

Note: I have tried both null:true and null:false in the Cards belongs_to migration. I also tried setting optional:true in the belongs_to :entry statement in the Card model


Solution

  • Unless you include an id in card_attributes then Rails sees this as a new record, so it just replaces the has_one with a newly created Card for you (which because of your dependent: :destroy option deletes the existing associated Card).

    Best to use a form.fields_for :card block in your form partial/view, which will automatically add the hidden id tag for an existing Card.