'ActiveRecord: Why is has_many dependent: :destroy not working?

For some reason, I'm getting PG::ForeignKeyViolation: ERROR after destroying a record.

Here I have the migration

  create_table :vacation_transactions do |t|
  t.belongs_to  :vacacion, index: true, foreign_key: true, null: true
  t.references  :vacacion_percibida, index: true, foreign_key: true, null: true
  t.timestamps
end

And here I have the models

class Vacacion < ApplicationRecord
  has_many :vacation_transactions, dependent: :destroy
end

class VacacionPercibida < ApplicationRecord
  has_many   :vacation_transactions, dependent: :nullify
end

class VacationTransaction < ApplicationRecord
  belongs_to :vacacion, optional: true
  belongs_to :vacacion_percibida, optional: true
end

Here I have an example: vacacion with id=348, a vacacion_percibida with id=950 and a vacation_transaction with

  #<VacationTransaction:0x00007f390901cc48> {
                           :id => 20,
                  :vacacion_id => 348,
        :vacacion_percibida_id => 950,
                   :created_at => some_date,
                   :updated_at => some_date
    }

But when I try to destroy the vacacion with id=348 the nightmare happens

  Vacacion.find(348).destroy! 
  # PG::ForeignKeyViolation: ERROR:  update or delete on table "vacacions" violates foreign key constraint "fk_rails_ae595e109b"
  # on table "vacation_transactions" DETAIL:  Key (id)=(348) is still referenced from table "vacation_transactions"

  # if I do the next lines I get the same error
  VacationTransaction.find(20).destroy! # cool
  VacationTransaction.find(20) # ActiveRecord::RecordNotFound, that means the record is destroyed
  Vacacion.find(348).destroy! # Same PG::ForeignKeyViolation: ERROR

I tried debugging ActiveRecord while destroying the vacacion with id=348 and I found this

# lib/active_record/associations/has_many_association.rb
when :destroy
  load_target.each { |t| t.destroyed_by_association = reflection }
  # load_target actually has the vacation_transaction record to destroy
  destroy_all
  # it actually destroys the vacation_transaction, in the console I can see the DELETE FROM "vacaciones_percibidas" WHERE "vacaciones_percibidas"."id" = $1
  # but the error still happens
else
  delete_all
end

Also, this problem only happens with the vacacion_id FK and only in a small amount of records of Vacacion

I'm using ruby 2.7.4p191, Rails 6.0.4.1, ActiveRecord 6.0.4.1

So, what am I missing?

Thanks.



Solution 1:[1]

So, I tried to remove the FK and adding it again and the problem persisted. In a last attempt I removed and added again the FK but specifying the cascade parameter add_foreign_key :vacation_transactions, :vacacions, on_delete: :cascade and now the problem is solved.

I still don't why I have to specify this in this specific FK and if this solution is going to trigger another problems in the future.

Edit: After a long time, I realized that I had problems with a dependent: :destroy and some after_save callbacks. A destroy of a vacacion triggered the destroy of another associated record that had an after_destroy that created another record associating the original vacacion with a vacation_transaction, so at the end when ActiveRecord tries to delete the origin vacation it was still referenced because of my after_destroy code in the associated model.

Of course, this never crossed my mind, that's why in my original question I didn't even bothered to share the callbacks of my models.

The solution was to change the order of my callbacks and association because Rails executes these in the same order you write them in your model

Solution 2:[2]

I think you need to modify your vacation model and add accepts_nested_attributes_for

class Vacacion < ApplicationRecord
  has_many :vacation_transactions, dependent: :destroy
  accepts_nested_attributes_for :vacation_transactions, allow_destroy: true
end

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Victor Luna