rails3_acts_as_paranoidまとめ

## 概要
### [rails3_acts_as_paranoid](https://github.com/goncalossilva/rails3_acts_as_paranoid  "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の中で依存関係にあるレコードを操作する場合、削除実行順に要注意