PG::ForeignKeyViolation: ERROR: update or delete on table "xxx" violates foreign key constraint
From the fine manual:
has_many(name, scope = nil, options = {}, &extension)
[...]
:dependent
Controls what happens to the associated objects when their owner is destroyed. Note that these are implemented as callbacks, and Rails executes callbacks in order. Therefore, other similar callbacks may affect the:dependent
behavior, and the:dependent
behavior may affect other callbacks.
:destroy
causes all the associated objects to also be destroyed.:delete_all
causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).- [...]
So :delete_all
does take care of foreign keys but, since no callbacks are invoked, it only goes one level deep. So this in Company
:
has_many :projects, dependent: :delete_all
means that calling #destroy
on a company will directly delete the associated projects
from the database. But that won't see this:
has_many :tasks, dependent: :delete_all
that you have in Project
and you end up trying to delete projects that are still referenced in tasks
as the error message indicates.
You could switch all your associations to dependent: :destroy
, this will pull everything out of the database before destroying them and callbacks will be called (which will load more things out of the database only to destroy them which will load more things out of the database...). The end result will be a lot of database activity but all the foreign keys will be properly followed.
Alternatively, you could put the logic inside the database where it usually belongs by specifying on delete cascade
on the foreign key constraints:
CASCADE specifies that when a referenced row is deleted, row(s) referencing it should be automatically deleted as well
Your add_foreign_key
calls would look like:
add_foreign_key "projects", "companies", on_delete: :cascade
add_foreign_key "tasks", "projects", on_delete: :cascade
add_foreign_key "task_times", "tasks", on_delete: :cascade
in this case. You'd probably want to leave the dependent: :delete_all
s in your models as a reminder as to what's going on, or you could leave yourself a comment.