Ruby

【Rails】ActiveRecordの:dependent使い分けまとめ【: destroy, :delete, :nullify】

RailsのDependentについて

ActiveRecordのdependentオプションとは

dependentオプションとは、モデルが削除されるときの、関係づけされたモデルに対する挙動を定義するものです。

たとえばこちらのコードの場合、Heroオブジェクトが削除されたときの、Weaponオブジェクトに対する挙動を定義しています。

dependentで設定できる値は、大きく分けて4つあります。

dependent: destroy

こちらを指定すると、削除時に指定したモデルに対してdestroyが実行されるようになります

例えばこのコードの場合、Heroモデルが削除されたら、Weaponモデルに対してdestroyが実行されるわけです。

destroyが実行されるので、それに関連した処理も逐次実行されていくことになります。

before_destroyなどdestroyに対するフックが実行されますし、孫クラスにもdependent :destroyがあれば、孫クラスも合わせて削除されます。

それは反面、実行すべき処理が多いというデメリットでもあります。

クエリだけ見ても最初にSELECTが実行され、has_manyの場合は意識しないと1個1個SELECTされてしまうのが欠点ですね。

使うタイミング

  1. 削除時に、指定したモデルのレコードも削除したい。(レコード数が増えると困る)
  2. before_destroyや、孫クラスのdependentなど、destroyに関連したフックを実行したい。
  3. 速度・サーバ負荷が気にならない。

dependent: delete / delete_all

belongs_to, has_oneの場合は:delete、has_manyの場合は:delete_allとなります。

delete / delete_allを指定すると、削除時に指定したモデルに対してDELETEクエリが直接実行されます。

DBからレコードを削除するという必要最低限の処理のみが行われるため、destroyと比較して処理が早いです。

また関連した処理も実行されないため、before_destroyが実行されると困る場合などでも使えるかもしれません。

使うタイミング

  1. 削除時に、指定したモデルのレコードも削除したい。(レコード数が増えると困る)
  2. before_destroyや、孫クラスのdependentなど、destroyに関連したフックを実行したくない。
  3. 速度・サーバ負荷が気になる。

dependent: nullify

nullifyを指定すると、削除時に指定したモデルの外部キーにnullが入れられます。

こちらのコードの場合、Weaponsテーブルの該当レコードにおける、hero_idの値がNULLにUpdateされるわけですね。

削除されることはないため、残ったレコードは永遠に残り続けることになります。

したがって、いずれテーブル内の無駄なレコードを整理する必要が出てくることは想定しておきましょう。

またクエリだけ見ると、updateのみなのでdeleteより負荷は軽いです。

使うタイミング

  1. 削除されたレコードは残しておきたいが、参照先のない外部キーは防ぎたい。
  2. レコードが増え続けても問題がない、もしくは深夜バッチなど自分のタイミングで削除を行いたい。
  3. DELETEする負荷に耐えられない。

dependent: restrict_with_exception / restrict_with_error

削除時に関連づけられたレコードが存在するときに、例外やエラーを発生させます。

  • restrict_with_exception:例外を発生させる。(DeleteRestrictionErrorがraiseする)
  • restrict_with_error:エラーとなる。(ActiveRecordのerrorとして扱われる)

ですのでシンプルに子要素があるときに削除されては困る場合に使うことになるでしょう。

たとえば「写真が存在するアルバムなので削除できません」とエラーメッセージを出したいときなどですね。

使うタイミング

  1. 関連づけられたレコードがあるときに削除されたら困る。

まとめ:ActiveRecordのdependentオプションとは

ActiveRecordのdependentオプションについて説明していきました。

dependentで削除時の挙動を指定するということは、dependentを設定しないと子要素に対しては何もされないということでもあります。

削除時に何か実行したい処理がある場合は、忘れずに設定するようにしましょう!