こんにちは、鈴木です。
Rails3 で論理削除をサポートするライブラリ rails3_acts_as_paranoid をご紹介します。
- rails3_acts_as_paranoid (https://github.com/goncalossilva/rails3_acts_as_paranoid)
rails3_acts_as_paranoid は Rails2 時代にあった acts_as_paranoid の Rails3 対応版です。
※2013/07/25 追記: Rails4 対応版は rails4_acts_as_paranoid です。(see 「Rails4 ライブラリ対応状況調査」)
論理削除とは
アプリケーション上でデータを削除する操作をした場合の方式に、物理削除と論理削除があります。
物理削除は、アプリケーション上で削除する操作をすると、実際にデータベースのレコードも削除する方式のことです。
論理削除は、あらかじめ削除フラグや削除日時のカラムを持たせておき、削除する操作が行なわれた時にはそのカラムに値を設定するだけの方式です。
論理削除を採用した場合は、当然ながら参照系の処理で論理削除されていないデータのみを取得するように注意する必要があります。
rails3_acts_as_paranoid は論理削除をサポートするライブラリです。
rails3_acts_as_paranoid の導入
Gemfile に以下の行を追加します。
1 |
gem 'rails3_acts_as_paranoid' |
bundle install します。
1 |
bundle install |
テーブルにカラムを追加する
テーブルに論理削除用のカラムを追加します。
例として users テーブルに論理削除用のカラムを追加する場合のマイグレーションは以下のようになります。
1 2 3 4 |
def change # 削除日時. add_column :users, :deleted_at, :timestamp end |
※カラムは boolean 型や string 型などにすることもできます。
モデルの設定
モデルクラスには、以下のように「acts_as_paranoid」を追加します。
1 2 3 4 5 |
class User < ActiveRecord::Base acts_as_paranoid end |
動作確認
それでは rails console で動作確認しましょう。
rails console で確認すると、destroy メソッドを呼び出したときに UPDATE で deleted_at に値が設定されたことが分かります。
1 2 3 4 5 6 7 8 |
> user = User.find(1) User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 AND ("users"."deleted_at" IS NULL) LIMIT 1 [["id", 1]] => #<User id: 1, name: "taro", deleted_at: nil> > user.destroy (0.1ms) BEGIN SQL (0.4ms) UPDATE "users" SET deleted_at = '2012-12-29 03:48:00.756046' WHERE "users"."id" = 1 AND ("users"."deleted_at" IS NULL) (24.6ms) COMMIT => #<User id: 4, name: "taro", deleted_at: "2012-12-29 03:48:00"> |
User.find(1) で ID=1 のレコードを取得し、destroy メソッドを呼び出しています。
通常 destroy メソッドを呼び出すと DELETE でレコードが削除されますが、代わりに UPDATE で deleted_at に値が設定されていることが分かります。
もう一度 ID=1 で find するとどうなるでしょうか。
1 2 3 |
> User.find(1) User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 AND ("users"."deleted_at" IS NULL) LIMIT 1 [["id", 1]] ActiveRecord::RecordNotFound: Couldn't find User with id=1 [WHERE ("users"."deleted_at" IS NULL)] |
ActiveRectod::RecordNotFound が発生しました。
rails3_acts_as_paranoid を使用すると、デフォルトで論理削除されたレコードは検索対象外となります。
論理削除されたレコードを取得する
デフォルトで論理削除されたレコードが検索対象外となるのは便利なのですが、論理削除されたレコードも検索対象に含めたい場合があります。
例えば、管理機能では論理削除されたレコードも表示したい、という場合があります。
論理削除されたレコードも検索対象に含める場合は、以下のように with_deleted を使用します。
1 2 |
# 論理削除されたレコードも検索対象に含める. User.with_deleted.find(1) |
論理削除されたレコード「だけ」を検索対象としたい場合は only_deleted を使用します。
1 2 |
# 論理削除されたレコードのみを検索対象とする. User.only_deleted.find(1) |
物理削除する
論理削除ではなく物理削除、本当にデータベースからレコードを消してしまいたい場合は、destroy! または delete_all! を使用します。
1 2 3 |
User.find(1).destroy! User.where(...).delete_all! |
また、論理削除済みのオブジェクトをもう一度 destroy すると、物理削除されます。
1 2 3 4 5 6 7 |
user = User.find(1) # 一回目は論理削除. user.destroy # 二回目は物理削除. user.destroy |
論理削除を取り消す
論理削除済みのオブジェクトを復旧したい(論理削除を取り消したい)場合は、recover を使用します。
1 2 3 4 5 6 7 8 9 |
user = User.find(1) # 論理削除する. user.destroy user.deleted? # => true # やっぱりやめる. user.recover user.deleted? # => false |
バリデーション
Rails が標準で提供する validates_uniqueness_of は、論理削除されているかどうかを考慮せずに重複チェックを行ないます。
論理削除されているものは除外して重複チェックするには、以下のようにします。
1 2 3 4 5 6 |
class User < ActiveRecord::Base validates_as_paranoid validates_uniqueness_of_without_deleted :name end |
validates_as_paranoid により validates_uniqueness_of_without_deleted が使用可能になります。
現時点では新しい形式のバリデーション( validates :name, ... 形式)には対応していないようです。
関連オブジェクト(アソシエーション)
belongs_to で関連オブジェクトを設定した場合はどのような動作になるのでしょうか。
例としてユーザはグループに属する(belongs_to)とします。
1 2 3 4 5 6 7 8 9 10 |
# グループ. class Group < ActiveRecord::Base acts_as_paranoid end # ユーザ. class User < ActiveRecord::Base acts_as_paranoid belongs_to :group end |
このとき、ユーザの属するグループが論理削除されていると、「user.group」のように参照すると nil が返されます。
つまり、デフォルトでは論理削除された関連オブジェクトは取得できません。
論理削除済みオブジェクトも取得したい場合は、以下のように :with_deleted オプションを使用します。
1 2 3 4 5 6 7 8 9 10 |
# グループ. class Group < ActiveRecord::Base acts_as_paranoid end # ユーザ. class User < ActiveRecord::Base acts_as_paranoid belongs_to :group, :with_deleted => true end |
まとめ
今回、ここで紹介するために改めて rails3_acts_as_paranoid のドキュメントを読みました。
今まで「論理削除するなら (rails3_)acts_as_paranoid を入れておけばいいや」くらいにしか思っていませんでしたが、意外と機能が多いことに気付きました。
良く知っているつもりのライブラリでも、たまにはドキュメントを読み返してみると新しい発見があって良いかもしれません。