Rails4.2.6 + Devise + kakurenbo-putiで論理削除を実装
私が論理削除で使うライブラリは専らkakurenbo-putiです。 ActiveRecordと疎結合なので、ハマることが少ないです。
今回はDeviseと合わせてみました。
kakurenbo-putiを使えるようにする
Gemfileに以下を追加します。
gem 'kakurenbo-puti'
bundle install
してから、カラムを追加しましょう。
rails g migration AddSoftDestroyedAtToUser soft_destroyed_at:datetime:index
bin/rake db:migrate
を実行します。
あとは、Modelにsoft_deletable
を入れるだけ。
class User < ActiveRecord::Base
soft_deletable
end
こうすると、論理削除用のメソッドが生えてきます。
emailのユニーク制約を外す
せっかく論理削除を実装しても、メールアドレスの重複を外せないといけません。 重複を外しましょう!
bin/rails g migration RemoveEmailIndexFromUser
生成されたマイグレーションファイルを修正します。
class RemoveEmailIndexFromUser < ActiveRecord::Migration
def up
remove_index :users, :email
add_index :users, [:email, :soft_destroyed_at], unique: true
end
def down
remove_index :users, [:email, :soft_destroyed_at]
add_index :users, :email, unique: true
end
end
emailのユニーク制約をやめて、emailとsoft_destroyed_atでユニーク制約つけています。
Userモデルのバリデーションをやり直す
deviseを使うと、なんと問答無用でemailのユニーク制約が付いてしまいます。
こうなると、バリデーションをキャンセルしないといけません…。 どうすればいいのかわかってなかったのですが、ググったらいい感じのコードに出会えました。 emailに関わるバリデーションを削除する方法です。
https://gist.github.com/brenes/4503386
まず、emailに関わるバリデーションを削除した後、再度定義しなおせばよさそうです。
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable, :timeoutable
soft_deletable
# Deviseを使うと、問答無用でemailがユニーク扱いになる。
# それだと論理削除した際に再登録できないので、一旦emailに関する検証を削除する
# https://gist.github.com/brenes/4503386
_validators.delete(:email)
_validate_callbacks.each do |callback|
if callback.raw_filter.respond_to? :attributes
callback.raw_filter.attributes.delete :email
end
end
# emailのバリデーションを定義し直す
validates :email, presence: true
validates_format_of :email, with: Devise.email_regexp, if: :email_changed?
validates_uniqueness_of :email, scope: :deleted_at, if: :email_changed?
end
これで、削除済みのユーザーが再度同じメールアドレスで登録することができるようになりました。 ここには書きませんが、ちゃんとrspecでテスト書いていますのでちゃんと検証済みです。
論理削除済みのユーザーをログインできなくする
まだ論理削除済みであっても、deviseのログインフォームからログインできてしまいます。 ログインできないようにしましょう。
Userモデルに、以下を追加します。
class User < ActiveRecord::Base
# (略)
def self.find_for_authentication(warden_conditions)
without_soft_destroyed.where(email: warden_conditions[:email]).first
end
end
このfind_for_authentication
メソッドは、ログイン条件をカスタマイズしたい場合にoverrideしてくれと書いてあったので、
without_soft_destroyed
スコープを追加して、論理削除済みユーザーを除外しています。
これを追加した後、論理削除ユーザーでログインしようとしたらできないことを確認しました。
以上で、終わりです。
まとめ
deviseとkakurenbo-putiの組み合わせはいい。