おはようございます。久しぶりの投稿ですが今回は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を使うのがいいかと思います。
今日はここまでになります。ここまで読んでいただきありがとうございました。
> 関係のない子レコードが残ってしまうのを防ぐため、きちんとdependentを考慮してモデル設計をして削除にはdestroy_allを使うのがいいかと思います
たしかにそうなのですが、 destroy_allはメモリ圧迫のデメリットもあるので、子レコード の有無とかテーブルのサイズとかあたりも考慮しつつ delete_all もつかったほうがいいと思いますよ。( バッチ処理になったから destroy_allのデメリットは少しへったけど)