その他

【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
酒井 駿
名古屋工業大学大学院卒業後、豊田合成(株)で品質管理を経験し、その後スタートアップ・マネーフォワードを経て、2024年11月に株式会社EGGHEAD創業。 製造業とエンジニアリング、両方の現場の知見を活かし、製造業における生成AIを活用した業務改善やシステム開発を支援します。

POSTED COMMENT

  1. match8969 より:

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

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