はじめに
Ruby on Railsで複数のレコードを削除する際に使用されるメソッドとしてRelation#delete_all
とCollectionProxy#delete_all
があります。
この記事では両者の違いをまとめます。
Relation#delete_all
とCollectionProxy#delete_all
の違い
まずは両者の基本的な違いを確認します。
Relation#delete_all
Relation#delete_all
はActive RecordのRelation
オブジェクトに対して使用されるメソッドです。
指定した条件に合致するレコードを一括で削除します。
SQLのDELETE文を直接発行するため非常に高速に動作しますが、削除時にモデルのコールバックやバリデーションは実行されません。
使用例
# ステータスが'inactive'のユーザーを一括削除
User.where(status: 'inactive').delete_all
CollectionProxy#delete_all
一方、CollectionProxy#delete_all
はActive Recordの関連付けを通じてアクセスされるオブジェクトに対して使うメソッドです。
has_one
やhas_many
などの関連づけられたコレクションに対して一括削除を行います。
使用例
# 投稿に紐づくコメントを削除
post = Post.first
post.comments.delete_all # postsテーブルとcommentsテーブルの関連付けを通して削除
引数の取り扱いの違い
Relation#delete_all
とCollectionProxy#delete_all
は引数の扱いが異なります。
実際のRailsのコードで確認します。
Relation#delete_all
Relation#delete_all
は引数を受け取りません。
参考:Relation#delete_all
の定義
def delete_all
return 0 if @none
invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|
value = @values[method]
method == :distinct ? value : value&.any?
end
if invalid_methods.any?
raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
end
model.with_connection do |c|
arel = eager_loading? ? apply_join_dependency.arel : build_arel(c)
arel.source.left = table
group_values_arel_columns = arel_columns(group_values.uniq)
having_clause_ast = having_clause.ast unless having_clause.empty?
key = if model.composite_primary_key?
primary_key.map { |pk| table[pk] }
else
table[primary_key]
end
stmt = arel.compile_delete(key, having_clause_ast, group_values_arel_columns)
c.delete(stmt, "#{model} Delete All").tap { reset }
end
end
CollectionProxy#delete_all
CollectionProxy#delete_all
は内部的に引数dependent
オプションを受け取る実装となっています。
ただしユーザーが直接引数を渡すことは想定されておらず、あくまでもActive Record内部の処理用です。
関連付けを定義する際、以下のようにdependent
オプションを付与するかと思います。
class User < ApplicationRecord
has_many :posts, dependent: :destroy # dependentオプションを設定
end
ここで設定した内容に応じて、dependent
オプションに引数が渡され、CollectionProxy#delete_all
の挙動が変わるのです。
参考:CollectionProxy#delete_all
の定義
def delete_all(dependent = nil)
@association.delete_all(dependent).tap { reset_scope }
end
おわりに
-
Relation#delete_all
: 引数を受け取らない -
CollectionProxy#delete_all
: Active Recordの内部で引数を受け取る
恥ずかしながら両者の違いを意識せず同じdelete_all
メソッドとして使っていました。
今後はこういった、Railsの内部挙動にも注意を払っていきたいです。
参考