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

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

dependent: :destroyとdependent: :delete_allの違い【Rails】

f:id:y_hakoiri:20191102121842j:plain

関連モデルを一緒に削除してくれるdependent: :destroydependent: :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文が走るので、

  1. ActiveRecordの機能が使えない
  2. より高速(らしい)

1. ActiveRecordの機能が使えない

実は今回はここでハマっていた。

ActiveRecordの機能が使えないということは、コールバックも実行されないということ。

例えばbefore_destroyのコールバックが定義されていたとしても、ActiveRecordをすっ飛ばして直接DBからレコードを削除してくれてしまうので、削除前に何らかの処理をしたくて定義したはずのbefore_destroyが呼ばれない。

または論理削除を想定している場合もうまくいかない。直接DBから消してしまうので、物理削除になってしまってた。

2. より高速(らしい)

これは自分で確かめたわけではないけれど、今回色々ググってる過程でたくさん目にした意見。

確かに直接SQLが走るから高速なんだろうなという予測はつくものの、そもそもActiveRecordを介しているかいないかの違いが大きすぎるので、本来比べるべきところではない気がする、、

まとめ

  • destroydelete_allの違いはActiveRecordを介するか介さないか
  • ActiveRecordの機能やgemによる論理削除を実現したいのであればdependent: :destroyを使用すべき

参考

delete, delete_all, destroy, destroy_allについて - Qiita

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