概要
- 論理削除を実現するgem
- deleted_at != nil のとき論理削除と判定する
- 導入が容易
使い方、削除時の挙動などについて調べたことをまとめる
環境
- ruby : 1.9.3p392
- rails : 3.2.12
- sqlite3 : 3.7.9
- rails3_acts_as_paranoid : 0.2.5
- この記事では、以下のモデルを使用する
class User < ActiveRecord::Base
acts_as_paranoid
attr_accessible :deleted_at, :name
has_many :orders, :dependent => :destroy
end
class Order < activeRecord::Base
acts_as_paranoid
attr_accessible :deleted_at, :name
end
使い方
- 論理削除を適用したいモデルにacts_as_paranoidを書くだけ
- dependentを指定することで、自身が削除されたとき子レコードを削除可能
- 子レコードにacts_as_paranoidの指定があれば子レコードも論理削除
- なければ物理削除される
Order.find(id).destroy #=> 論理削除
User.find(id).destroy #=> Userとそれに紐付くOrderが論理削除される
論理削除済レコードの取得
User.only_deleted #=> 論理削除済レコードのみ
User.with_deleted #=> 論理削除済レコードを含む
物理削除
- 破壊的メソッドの使用(destroy!, delete_all!)
User.find(id).destroy!
User.delete_all!
- 論理削除済レコードに対し再度論理削除実行でも物理削除される
User.find(id).destroy
User.only_deleted.first.destroy
論理削除済レコードの復元
- 論理削除済レコードに対してrecoverすることで復元される
- dependentで指定されたモデルで論理削除された子レコードがある場合、そのレコードも復元される
User.find(id).destroy
User.only_deleted.find(id).recover #=> Userとそれに紐付くOrderが復元される
- 子レコードの復元は、デフォルトでは親レコード削除から2分前後以内に削除されたものにフィルタリングされる
- この時間はrecovery_windowで指定可能
User.only_deleted.find(id).recover(:recovery_window => 30.seconds)
class User < ActiveRecord::Base
acts_as_paranoid :dependent_recovery_window => 10.minutes
attr_accessible :deleted_at, :name
has_many :orders, :dependent => :destroy
end
復元時のvalidation
- ユニーク制約をかけた場合でも、論理削除 -> 別レコード作成 -> 復元とするとレコードが重複してしまう場合がある
- validates_uniqueness_of_without_deleted で回避可能
user = User.create(:name => 'foo')
user.destroy
User.new(:name => 'foo').save
user.recover #=> 失敗
依存するレコードの論理/物理削除
- 削除系のメソッド、dependentで指定したメソッドによって、削除時の挙動が異なる
- 前述のUserを削除したときのUserとOrderの挙動を以下にまとめる
- dependentがdelete_allでも挙動は同じ
クラスメソッド
メソッド名 |
User |
Order |
User.delete(id) |
論理削除 |
|
User.destroy(id) |
論理削除 |
論理削除 |
User.delete_all |
論理削除 |
|
User.delete_all! |
物理削除 |
|
User.destroy_all |
論理削除 |
論理削除 |
インスタンスメソッド
メソッド名 |
User |
Order |
User.first.delete |
論理削除 |
|
User.first.destroy |
論理削除 |
論理削除 |
User.first.destroy! |
物理削除 |
物理削除 |
dependentがdeleteの場合
- 親レコードが物理削除であっても、子レコードは論理削除となる
- 親レコードが論理削除済でrecoverした場合、子レコードは復元されない
物理削除時の実行順
前述のUser, Orderにおいて、Userを物理削除した場合、以下の順で処理される
- Userに紐付くすべてのOrderレコードを論理削除
- Userに紐付くすべてのOrderレコードを物理削除
- Userを物理削除
先ほどのOrderに以下のItemが紐付いている場合
- Itemはacts_as_paranoidで論理削除対応, Orderのdependentでdestroy指定されているものとする
- Userを物理削除したとき、acts_as_paranoidを利用していない以下の処理と同等になる
User.find(id).orders.each do | order |
order.items.each { |item| item.update_attribute(:deleted_at, Time.now)
order.update_attribute(:deleted_at, Time.now)
end
User.find(id).orders.each do | order |
order.items.each { |item| item.destroy } # 物理削除
order.destroy
end
User.find(id).destroy! # 物理削除
- 孫レコード、子レコードが順に論理削除され、その後それぞれ物理削除される
- 物理削除時にはそれぞれのレコードが論理削除済のため参照できない
- itemが以下のようなコードだった場合、物理削除時にafter_destroyで実行されるsub_totalでorderが参照できず、削除に失敗してしまう
class Item < ActiveRecord::Base
acts_as_paranoid
belongs_to :order
attr_accessible :deleted_at, :order_id, :price
after_create :add_total
after_destroy :sub_total
def add_total
order.total += self.price
order.save!
end
def sub_total
order.total -= self.price
order.save
end
end
- before_save, after_destroy等のrollbackの中で依存関係にあるレコードを操作する場合、削除実行順に要注意