PG::ForeignKeyViolation:错误:表“xxx”上的更新或删除违反了外键约束

2024-01-12

我有几个表,它们具有与之关联的外键约束,每个表都以分层方式引用另一个表,如下所述。

当我试图摧毁一家至少有 1 个项目、至少有 1 个任务、至少有 1 个任务时间的公司时,就像这样......

irb(main):014:0> Company.first.destroy

我得到以下输出和错误。我现在的印象是,只要dependent: :delete_all不处理外键约束,这是真的吗?如果是这样,我该如何处理这种情况?我知道关于before_destroy回调,在这种情况下我必须使用它吗?如果是这样,如何暂时禁用外键约束以销毁所有关联的行?更令人困惑的是,我有一个旧的 Rails 项目,它设置了相同的表/模型,只是它是一个model_a has_many model_bs, dependent: delete_all与外键约束的关系,我可以ModelB.destroy_all它有效,所以我不明白。我还读过一些帖子,其中在表上设置级联删除有效,有些帖子说如果您自己在代码中处理它,则不需要这样做;如果解决方案不是太复杂,我想在我的代码中处理这个问题。

Company Load (0.4ms)  SELECT  "companies".* FROM "companies" ORDER BY 
                              "companies"."id" ASC LIMIT $1 [["LIMIT", 1]]
   (0.2ms)  BEGIN
             SQL (0.9ms)  DELETE FROM "projects" 
                          WHERE "projects"."company_id" = $1 [["company_id", 3]]
   (0.1ms)  ROLLBACK
             Traceback (most recent call last):
   1: from (irb):13
             ActiveRecord::InvalidForeignKey (PG::ForeignKeyViolation: ERROR:  update or delete on table "projects" violates foreign key constraint "fk_rails_02e851e3b7" on table "tasks"
                          DETAIL:  Key (id)=(4) is still referenced from table "tasks".
                        : DELETE FROM "projects" WHERE "projects"."company_id" = $1)

Schema

# /db/schema.rb

create_table "companies", force: :cascade do |t|
...
end

create_table "projects", force: :cascade do |t|
...
end

create_table "tasks", force: :cascade do |t|
...
end

create_table "task_times", force: :cascade do |t|
...
end
...

add_foreign_key "projects", "companies"
add_foreign_key "tasks", "projects"
add_foreign_key "task_times", "tasks"

Models

# /models/company.rb

class Company < ApplicationRecord
  has_many :projects, dependent: :delete_all
...
end

# /models/project.rb

class Project < ApplicationRecord
  has_many :tasks, dependent: :delete_all
...
end

# /models/task.rb

class Task < ApplicationRecord
  has_many :task_times, dependent: :delete_all
...
end

# /models/task_time.rb

class TaskTime < ApplicationRecord
...
end

来自精美手册 http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many:

has_many(名称、范围 = nil、选项 = {}、&扩展名)
[...]

  • :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导致所有关联的对象也被销毁。
    • :delete_all导致所有关联的对象直接从数据库中删除(因此不会执行回调)。
    • [...]

So :delete_all确实处理外键,但是由于没有调用回调,因此它只深入一层。所以这在Company:

has_many :projects, dependent: :delete_all

意味着调用#destroy在公司上会直接删除关联的projects从数据库中。但那不会看到这个:

has_many :tasks, dependent: :delete_all

你有的Project并且您最终尝试删除仍在中引用的项目tasks如错误消息所示。

您可以将所有关联切换到dependent: :destroy,这将从数据库中取出所有内容,然后再销毁它们,并且将调用回调(这将从数据库中加载更多内容,只是为了销毁它们,这将从数据库中加载更多内容......)。最终结果将是大量数据库活动,但所有外键都将得到正确遵循。

或者,您可以通过指定将逻辑放在它通常所属的数据库中on delete cascade关于外键约束 https://www.postgresql.org/docs/current/static/ddl-constraints.html#DDL-CONSTRAINTS-FK:

CASCADE 指定当删除引用的行时,引用它的行也应自动删除

Your add_foreign_key调用看起来像:

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

在这种情况下。你可能想离开dependent: :delete_all在您的模型中作为有关正在发生的事情的提醒,或者您可以给自己留下评论。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PG::ForeignKeyViolation:错误:表“xxx”上的更新或删除违反了外键约束 的相关文章

随机推荐