32
28

More than 5 years have passed since last update.

rails3_acts_as_paranoidまとめ

Last updated at Posted at 2013-03-11

概要

rails3_acts_as_paranoid

  • 論理削除を実現する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を物理削除した場合、以下の順で処理される

  1. Userに紐付くすべてのOrderレコードを論理削除
  2. Userに紐付くすべてのOrderレコードを物理削除
  3. 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の中で依存関係にあるレコードを操作する場合、削除実行順に要注意
32
28
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
28