記事の対象者
・ActiveRecord::InvalidForeignKey: Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint failsのエラーが出た初学者
・上記エラーでググって、dependent: :destroyを追加するものの解決しない方(状況が違ければ、すみません)
・ActiveRecordを通すdestroyと直接実行するSQLのdeleteの違いがわからない方
エラーが出た自身の状況
・モデル:bookが親、reviewが子という関係。又、reviewテーブルのbook_idに外部キー制約を設けている。
・以下のマイグレーションファイルを適用させようとrails db:migrateを実行し、execute 'DELETE FROM books;' の行でbookを削除しようとすると、エラーが発生。
※以下のマイグレーションファイルは、bookに外部キーuser_idをもたせようとしており、その前にuser_idが入っていないbook削除しようとしています。ここはあまり気にしなくて大丈夫です。
class AddUserIdToBooks < ActiveRecord::Migration[6.0]
def up
execute 'DELETE FROM books;' ##この行でuser_idが入っていないbooksを削除してから、
add_reference :books, :user, null: false, index: true ##booksに外部キーのuser_idを追加しようとしているマイグレファイルです。
end
def down
remove_reference :books, :user, index: true
end
end
ActiveRecord::InvalidForeignKey: Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`book_action_development`.`reviews`, CONSTRAINT `fk_rails_924a0b30ca` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`))
エラーを文を読む(外部キー制約が原因で削除できないようである)
外部キーが適切でなく、親の行(本の行)を削除できなかった。外部キー制約が失敗。Reviewの外部キーbook_idと参照先のbookの主キーidが原因で失敗している。と読みました。
ちなみにfk_rails_924a0b30caは、Sequel Proで確認すると外部キーの名前のようです。
エラーについて調べてみると、ReviewがBookを参照しているため、Bookのデータを削除できないというエラーのようです。もしBookを削除してしまうと、Reviewが参照しているはずのBookのデータが存在しないものになってしまい、これはいけないということで「削除できません!」というエラーが出ているようです。
dependent: :destroyを記述する(自身の場合、解決策とならなかった...)
上記のエラーで検索すると、
ReviewがBookを参照しているため、Bookのデータを削除できないというエラーだから、dependent: :destroyを記述することにより、bookとreviewが共に削除される設定にすれば解決するよ!という記事を見かけ、以下のようにdependent: :destroyを記述しました。
class Book < ApplicationRecord
has_many :reviews, dependent: :destroy ## dependent: :destroyを追記
has_one_attached :image
# belongs_to :user
validates :title, presence: true, length:{ maximum: 50}
validates :author, presence: true, length:{ maximum: 30}
end
そしてrails db:migrate実行、、、状況は変わらず同じエラーが出る。。
自身の状況では、dependent: :destroyを実行しても解決されませんでした。。なぜ?
executeの行がSQLを実行する為、dependent: :destroyが効かなかった(原因)
execute 'DELETE FROM books;'の行は、以下のRailsガイド(抜粋)にあるようにexecuteによりSQLを実行する行となっています。つまり、モデル、ActiveRecordを通さずにbookを削除しようとしている行になります。(書籍「現場Rails」の例文を思考停止で参考にしていたので、ここに気づくことができませんでした。)
これ故、モデルで設定したdependent: :destroyを設定しても効かない形になります。
Active Recordが提供するヘルパーの機能だけでは不十分な場合、executeメソッドで任意のSQLを実行できます。 Railsガイド(抜粋)より
モデル、Active Recordを通すdestroy_allで削除するよう変更し、dependent: :destroyを効かせる
execute 'DELETE FROM books;'の行をモデル、ActiveRecordを通してBookが削除できるようBook.destroy_allに変更します。(そうすると、dependent: :destroyが効きます)
※destroy_allは、すでにテーブルに存在するレコードを一括で削除するメソッドになります。
class AddUserIdToBooks < ActiveRecord::Migration[6.0]
def up
Book.destroy_all ##この行を変更し、ActiveRecordを通してBookを削除できるようにした。
add_reference :books, :user, null: false, index: true
end
def down
remove_reference :books, :user, index: true
end
end
これでrails db:migrateしてみると、無事成功し、reviewと紐づくbookを削除することができました。
##まとめ
・execute 'DELETE FROM books;'の行は、SQL文を実行する行なのでモデル・ActiveRecordを通さずに削除しようとする。故に、dependent: :destroyは効かない。
##参考記事
・dependent: :destroyの解説がわかりやすく書かれており、dependent: :destroyを実行すると子モデルから削除されることがわかりやすく書いておりました。
→https://pikawaka.com/rails/destroy_all
・こちらのブログにdestroyとDELETEの違いなどなど書かれていて、問題発見のきっかけとなりました🙇♂️
→https://www.y-hakopro.com/entry/2020/01/25/Cannot_delete_or_update_a_parent_row%3A_a_foreign_key_constraint_fails%E3%80%90MySQL%E3%82%A8%E3%83%A9%E3%83%BC%E3%80%91