関連モデルを一緒に削除してくれるdependent: :destroy
とdependent: :delete_all
について、違いがよく分かってなかったのでまとめました。
モデルを用意
models/user.rb
class User < ApplicationRecord has_many :articles end
models/article.rb
class Article < ApplicationRecord belongs_to :user end
migration
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
今回はUserモデルとArticleモデルを使用。
UserとArticleが1:Nの関係で、articlesテーブルにはuser_id
のカラムが入ります。
この状態だけだと、関連付けはされているけれどユーザーが削除された時に自動的にArticleは消えてくれない。そのため、user_id
が空のArticleがデータベースに複数残ってしまい、多分いろんなところでバグが発生する。
基本的なアプリケーションであれば普通親モデルが消されたら子モデルも削除されてほしいと思うので、ここでdependent:
オプションの登場。
dependent: :destroy
models/user.rb
class User < ApplicationRecord has_many :articles, dependent: :destroy end
こうすることで、ユーザーが削除された時に記事も削除してくれる。
dependent: :delete_all
models/user.rb
class User < ApplicationRecord has_many :articles, dependent: :delete_all end
こうすることで、ユーザーが削除された時に記事も削除してくれる。
.....え、一緒じゃん。
と思うかもしれないけど、消し方に違いがある。
何が違うのか
:destroyを指定すると、関連付けられたオブジェクトもすべて同時にdestroyされます。 :delete_allを指定すると、関連付けられたオブジェクトはすべてデータベースから直接削除されます。このときコールバックは実行されません。
引用:Active Record の関連付け - Railsガイド
ここのキモは、「データベースから直接削除されます」の部分。
要するに、destroy
の場合はActiveRecordを介して削除が行われるが、delete_all
の場合はActiveRecordを介さずに、直接SQL文が走ってレコードが削除される。
これによってどんな違いがあるかとういうと、以下の通り。
delete_allは直接SQL文が走るので、
- ActiveRecordの機能が使えない
- より高速(らしい)
1. ActiveRecordの機能が使えない
実は今回はここでハマっていた。
ActiveRecordの機能が使えないということは、コールバックも実行されないということ。
例えばbefore_destroy
のコールバックが定義されていたとしても、ActiveRecordをすっ飛ばして直接DBからレコードを削除してくれてしまうので、削除前に何らかの処理をしたくて定義したはずのbefore_destroy
が呼ばれない。
または論理削除を想定している場合もうまくいかない。直接DBから消してしまうので、物理削除になってしまってた。
2. より高速(らしい)
これは自分で確かめたわけではないけれど、今回色々ググってる過程でたくさん目にした意見。
確かに直接SQLが走るから高速なんだろうなという予測はつくものの、そもそもActiveRecordを介しているかいないかの違いが大きすぎるので、本来比べるべきところではない気がする、、
まとめ
destroy
とdelete_all
の違いはActiveRecordを介するか介さないか- ActiveRecordの機能やgemによる論理削除を実現したいのであれば
dependent: :destroy
を使用すべき