dependent: :destroyオプションとActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed)について
dependent: :destroyオプションは、親モデルのデータが削除されるときにそれに関連していた子モデルのデータも削除することができるオプション。
このオプションを設定しないと、親モデルのテーブルのデータを削除するときにエラーになる。
上記のようなテーブルがあり、ownersテーブルのid=3 高橋さんを削除すべく以下のコマンドを実行。
irb(main):002:0> Owner.find(3).destroy
TRANSACTION (0.1ms) begin transaction
Owner Destroy (3.0ms) DELETE FROM "owners" WHERE "owners"."id" = ? [["id", 3]]
TRANSACTION (0.6ms) rollback transaction
Traceback (most recent call last):
ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed)
エラー文は、「ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY 制約が失敗しました)」という意味です。
つまり、親テーブルのownersテーブルにあるid=3 高橋さんを消そうとすると、外部キー制約(他のテーブルに参照・依存するようにカラムにつける制約のこと)により、データの整合性が保たれなくなり、id=3 高橋さんを参照している子テーブルcatsテーブルのid = 3 name = ハナ owner_id = 3のデータのowner_id を表示できなくなり、エラーになる。
そこでdependent: :destroyオプションを使用し、親モデルのデータが削除された時に子モデルのデータも一緒に削除できるようにし、データの整合性を保つようにする。
書き方は下記の通り。
class Owner < ApplicationRecord
has_many :cats, dependent: :destroy
end
そして再度、ownersテーブルのid=3 高橋さんを削除すべく以下のコマンドを実行してみると、、、
irb(main):002:0> Owner.find(3).destroy
Owner Load (0.2ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
TRANSACTION (0.1ms) begin transaction
Cat Load (0.2ms) SELECT "cats".* FROM "cats" WHERE "cats"."owner_id" = ? [["owner_id", 3]]
Cat Destroy (0.5ms) DELETE FROM "cats" WHERE "cats"."id" = ? [["id", 3]]
TRANSACTION (10.5ms) commit transaction
=> #<Owner id: 3, name: "高橋", created_at: "2022-09-30 23:15:04.894767000 +0000", updated_at: "2022-09-30 23:15:04.894767000 +0000">
(Id = 3 高橋さんが消えたか確認)
irb(main):002:0> Owner.find(3)
Owner Load (0.2ms) SELECT "owners".* FROM "owners" WHERE "owners"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
Traceback (most recent call last):
ActiveRecord::RecordNotFound (Couldn't find Owner with 'id'=3)→id= 3高橋さんのデータ削除完了