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

ActiveRecordのdependentオプションとは

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

たとえばこちらのコードの場合、Heroがdestroyされたときの、Heroに関連づけられたWeaponに対する挙動を定義しています。

class Hero < ApplicationRecord
  has_one :weapon, dependent: :nullify
end

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

dependent: destroy

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

class Hero < ApplicationRecord
  has_one :weapon, dependent: :destroy
end

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

Weapon.destroy # これと同じ処理が実行される。

関連した処理も実行される

destroyが実行されるので、それに関連した処理も逐次実行されていくことになります。
before_destroyなどdestroyに対するフックが実行されますし、孫クラスにもdependent :destroyがあれば、孫クラスも合わせて削除されます。

それは反面、実行すべき処理が多いというデメリットでもあります。
関連が未取得の場合は最初にSELECTが実行されます。特にhas_manyの場合は1個1個SELECTされることがないよう注意しましょう。

pry(main)> hero.destroy (0.2ms)
BEGIN Weapon Load (0.4ms) SELECT `weapons`.* FROM `weapons` WHERE `weapons`.`id` = 1 LIMIT 1
# Weapon::before_destroyが実行されました。
SQL (0.4ms) DELETE FROM `weapons` WHERE `weapons`.`id` = 1

使うタイミング

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

dependent: delete / delete_all

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

Weapon.delete # これと同じ処理が実行される。

belongs_to, has_oneの場合は:deletehas_manyの場合は:delete_allと記述します。

DBからレコードを削除するという必要最低限の処理のみが行われるため、destroyと比較して処理が早いです。
また関連した処理も実行されないため、before_destroyなどの関連した処理を実行されると困る場合などでも使えます。

使うタイミング

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

dependent: nullify

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

class Hero < ApplicationRecord
  has_one :weapon, dependent: :nullify
end

こちらのコードの場合、Weaponテーブルの該当レコードにおける、hero_idNULLUPDATEされるわけですね。

削除されることはないため、残ったレコードは永遠に残り続けることになります。
そのため記録としてデータを残しておきたいときなどに便利です。
またUPDATEのみなのでDELETEより負荷は軽く、削除の負荷に耐えられないような高負荷サービスでも選択肢に入るでしょう。

とはいえ無尽蔵にデータが増え続けるため、いずれテーブル内の無駄なレコードを整理する必要が出てくることは想定しておく必要があります。

使うタイミング

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

dependent: restrict_with_exception / restrict_with_error

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

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

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

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

使うタイミング

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

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

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

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

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


@dorarep
小学生の頃からフリーゲーム作ってました。今はフリーランスでフルスタックエンジニアしてます。