Ruby on Rails

【Rails】destroy_allとdelete_allの挙動のちがい

おはようございます。久しぶりの投稿ですが今回はdestroy_allとdelete_allの挙動について見ていきたいと思います。

destroy_all

このメソッドはActiveRecordを使って指定されたレコードをすべて削除します。ActiveRecordを使うので、dependentが設定されている場合はそれが適用され、またコールバックも行われます。

class Publisher < ApplicationRecord
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  belongs_to :publisher, optional: true
  has_one :category, dependent: :destroy
end
publisher = Publisher.first
 publisher.books.destroy_all

  TRANSACTION (0.3ms)  begin transaction
  Category Load (0.5ms)  SELECT "categories".* FROM "categories" WHERE "categories"."book_id" = ? LIMIT ?  [["book_id", 1], ["LIMIT", 1]]
  Category Destroy (1.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 1]]
  Book Destroy (0.2ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
  Category Load (0.1ms)  SELECT "categories".* FROM "categories" WHERE "categories"."book_id" = ? LIMIT ?  [["book_id", 2], ["LIMIT", 1]]
  Category Destroy (0.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 2]]
  Book Destroy (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 2]]
  Category Load (0.0ms)  SELECT "categories".* FROM "categories" WHERE "categories"."book_id" = ? LIMIT ?  [["book_id", 3], ["LIMIT", 1]]
  Category Destroy (0.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 3]]
  Book Destroy (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 3]]
  Category Load (0.1ms)  SELECT "categories".* FROM "categories" WHERE "categories"."book_id" = ? LIMIT ?  [["book_id", 4], ["LIMIT", 1]]
  Category Destroy (0.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 4]]
  Book Destroy (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 4]]
  Category Load (0.0ms)  SELECT "categories".* FROM "categories" WHERE "categories"."book_id" = ? LIMIT ?  [["book_id", 5], ["LIMIT", 1]]
  Category Destroy (0.1ms)  DELETE FROM "categories" WHERE "categories"."id" = ?  [["id", 5]]
  Book Destroy (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 5]]
  TRANSACTION (0.6ms)  commit transaction

発行されているSQLを見ると、それぞれのbookに紐付いたcategoryまで削除されているのが分かります。

  • destroy_allはActiveRecordを使ってレコードを削除する。
  • そのため、dependentで指定した子レコードに対する処理やコールバックが発火する。

delete_all

一方、delete_allはというとActiveRecordを使わずにSQLを直接実行して削除します。

Publisher.where(id: [1..3]).delete_all
  Publisher Destroy (5.0ms)  DELETE FROM "publishers" WHERE "publishers"."id" BETWEEN ? AND ?  [["id", 1], ["id", 3]]

さらに子レコードを指定して削除する場合、dependentの設定によって挙動が変わります。

class Publisher < ApplicationRecord
  has_many :books, dependent: :destroy
end

class Book < ApplicationRecord
  belongs_to :publisher, optional: true
  # 略
end

dependent: :destroyが設定されている場合はその子レコードを削除します。

publisher.books.delete_all

  Book Destroy (3.4ms)  DELETE FROM "books" WHERE "books"."publisher_id" = ?  [["publisher_id", 1]]
=> 5

一方、dependent: :nullifyが設定されている場合は子レコードを削除しません。

class Publisher < ApplicationRecord
  has_many :books, dependent: :nullify
end

class Book < ApplicationRecord
  belongs_to :publisher, optional: true
  # 略
end
publisher.books.delete_all

  Book Update All (3.2ms)  UPDATE "books" SET "publisher_id" = ? WHERE "books"."publisher_id" = ?  [["publisher_id", nil], ["publisher_id", 1]]
=> 5

delete_allを使うことはほとんどないかとは思いますが、このような挙動をしますということでした。

さいごに

関係のない子レコードが残ってしまうのを防ぐため、きちんとdependentを考慮してモデル設計をして削除にはdestroy_allを使うのがいいかと思います。
今日はここまでになります。ここまで読んでいただきありがとうございました。

ABOUT ME
sakai
東京在住の30歳。元々は車部品メーカーで働いていてましたが、プログラミングに興味を持ちスクールに通ってエンジニアになりました。 そこからベンチャー → メガベンチャー → 個人事業主になりました。 最近は生成 AI 関連の業務を中心にやっています。 ヒカルチャンネル(Youtube)とワンピースが大好きです!

POSTED COMMENT

  1. match8969 より:

    > 関係のない子レコードが残ってしまうのを防ぐため、きちんとdependentを考慮してモデル設計をして削除にはdestroy_allを使うのがいいかと思います

    たしかにそうなのですが、 destroy_allはメモリ圧迫のデメリットもあるので、子レコード の有無とかテーブルのサイズとかあたりも考慮しつつ delete_all もつかったほうがいいと思いますよ。( バッチ処理になったから destroy_allのデメリットは少しへったけど)