Rails4 + devise + paranoiaで論理削除を考慮したユーザーバリデーションを実現する
Rails でユーザー登録、認証機能を実装する際、deviseを使用することはわりと一般的かと思います。
またユーザーの論理削除を実現するための gem としてはparanoiaが有名かと思います。(Rails3 時代によく使われていた acts_as_paranoid をよりシンプルにしたやつ)
どちらも素晴らしい gem なのですがこれらを同時に使用した際、以下のような問題が発生しました。
- devise の
validatable
を使用してUser
モデルの validation を実装 (email, password 関連の validation が追加される)。 - paranoia の
acts_as_paranoid
メソッドを呼んで、ユーザーの論理削除をできるようにする。 email: hoge@hoge.com
のユーザー hoge、email: fuga@fuga.com
のユーザー fuga を用意する。fuga.destroy
で fuga を論理削除。hoge.update_attributes(email: 'fuga@fuga.com')
で hoge の email を更新しようとするとユニーク制約で validation エラー発生。
こんな感じで削除済のユーザーと同じ email を登録しようとするとユニーク制約にひっかかってしまって困りました。
今回はこれを通常のユーザーの email ユニーク制約は残しつつ、削除済みユーザーはその対象外となるよう修正していきたいと思います。
とりあえず validatable を外す
まずは deviseのvalidatable
を外して、必要なコードを本家からコピーしてくる。
base.class_eval do
validates_presence_of :email, if: :email_required?
#validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: password_length, allow_blank: true
end
必要そうなのはこの辺り。ただvalidates_uniqueness_of
はこのままだとダメなので一旦外す。
DB の index を修正する
devise 導入時の migration で
class DeviseCreateUsers < ActiveRecord::Migration
中略
add_index :users, :email, unique: true
end
のようにしていると思うので、その前提で。
これだとdeleted_at
に関係なくユニーク制約がついてしまっているので、これを修正するための migration ファイルを作成する。
class UpdateIndexUsersOnEmail < ActiveRecord::Migration
def up
remove_index :users, :email
execute 'CREATE UNIQUE INDEX unique_index_on_users_email ON users(email) WHERE deleted_at IS NULL'
end
def down
execute 'DROP INDEX unique_index_on_users_email'
add_index :users, :email, unique: true
end
end
こんな感じでdeleted_at
がnull
の場合のみユニークとする。これで DB 側の修正は OK のはず。
paranoia_uniqueness_validator を使う
次に Model 側の修正。
paranoia_uniqueness_validatorという gem を使用する。これはacts_as_paranoid
で
class User < ActiveRecord::Base
validates_as_paranoid
validates_uniqueness_of_without_deleted :email
end
のようにしていた機能がparanoia
ではparanoia_uniqueness_validator
という gem に切り離されたので、これを別途追加する必要があるといった感じ。
こちらの記事とか参考にさせていただきました。
んで、paranoia_uniqueness_validator
では以下のように validation を追加する。
class User < ActiveRecord::Base
validates :email, uniqueness_without_deleted: true, allow_blank: true, if: :email_changed?
end
これで期待通りの挙動になるよう修正できた、、はず。
まとめ
gem は便利だけど、思い通りにいかない時は自力で頑張る必要があるということですね。。世知辛い。 もしなにかおかしな所がございましたら、コメント等頂けると嬉しいです!!