箱のプログラミング日記。

渋谷の自社開発企業でRails書いてます。

Cannot delete or update a parent row: a foreign key constraint fails【MySQLエラー】

f:id:y_hakoiri:20191102121618j:plain

MySQLのエラーでちょっとハマったのでメモ。

models/user.rb

class User < ApplicationRecord
 has_many :articles, dependent: :destroy
end

models/article.rb

class Article < ApplicationRecord
 belongs_to :user
end

migration(article)

create_table "articles" do |t|
 t.references :user, foeign_key: true
 t.string     :title, null: false
 t.text       :text, null: false
 t.datetime   :created_at
end

親:ユーザー

子:記事(article)

articleテーブルのuser_idには外部キー制約を設けてます。

発生したエラー

この状態で親モデルであるユーザーを削除しようとした時にエラーがでた。

users_controller.rb

def destroy
 @user.delete
end

Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails....

何やら外部キー周りがおかしい様子。

外部キー制約とは

そもそも外部キー制約とは、親モデルに何らかの処理(updateやdeleteなど)が走った時に関連モデルに影響が出ないよう、勝手に親だけどうにかなってしまうのを制御するために使用するオプション。

今回の場合、articleモデルのuser_idカラムが空の状態の子モデルを発生させないように制約する。

マイグレーションファイルでt.references :モデルとすることで、モデル_idカラムを生成してくれる。

(ちなみにreferenceの貼り方は色々あるけど、この書き方をするとインデックスも自動で貼ってくれる。)

単純にリレーションをもたせたい場合はこれで良し。さらに外部キー制約をかけたい場合はfoeign_key: trueオプションをつける。

reference イコール 外部キー とちょっと勘違いしていた部分があった。

dependent: :destroy が効いてない?

あれ、でもdependent: :destroyをちゃんと書いてるので、親と一緒に子が消えてくれると思ったけど。。

子モデルも一緒に消えてくれたら、そもそも今回のようなエラーは出ないんじゃないか??と思いもう一度コントローラーを見てみると。

users_controller.rb

def destroy
 @user.delete
end

deleteになってた。

deleteメソッドは直接SQLが走るので、ActiveRecordを介さない。ということは、ちゃんと書いてるはずのdependent: :destroyが効いていなかった。

結果、親モデルと一緒に削除されるはずの子モデルだけレコードが残り、articleレコードのuser_idカラムが空になってしまい、「外部キー制約ついてるから親(ユーザー)だけ消せないよ!!」というエラーが出ていた。

解決策:destroyにする

users_controller.rb

def destroy
 @user.destroy
end

これでOK。

deleteとdestroyって間違ってても気づきづらい...見つけるのに時間かかってしまった。

まとめ

  • アソシエーションのために外部キーを貼ることと外部キー制約をつけることは別物。
  • 外部キーの貼り方には複数ある。マイグレーションの書き方によりインデックスがついたりつかなかったり、外部キー制約をつけられたりつけられなかったりする(ややこしいな)
  • deleteとdestroyの違いに注意

deleteとdestroyに関しては、dependentオプションに関しても同じことが言えるなと思った。

SQL文のDELETEを思い浮かべると分かるように、「delete」と言われたらRailsのActiveRecordではなくSQLが直接走るのだ!!ということを肝に命じなければ、、。

参考

Ruby on Rails - 投稿が外部制約によって削除ができない|teratail

Active Record の関連付け - Railsガイド

マイグレーションファイルの詳しい書き方は以下

Railsの外部キー制約とreference型について - Qiita