新しいRailsアプリを作るたびにやっている気がするので、一旦まとめておこうと思います。
今回公開する手順は、削除済みのメールアドレスで再登録可能な論理削除の実装方法です。
Deviseとは?
Deviseはアカウント認証管理のgemです。Webアプリケーションにほぼ必須である認証機能をほぼ網羅しています。
- アカウントの新規作成
- メールアドレスの確認
- パスワード忘れの問い合わせ
- パスワード間違えすぎたらアカウントロック
- その他諸々
論理削除とは?
論理削除とは、レコードは物理的に削除しないけれど、削除済ということにする、ということです。 物理的に削除してしまうと、そのアカウントに紐づいたデータが全て削除されてしまったり、データに紐づいていたユーザが誰だったかわからなくなり、困ったことになります。 そこで、論理削除することで、ログインはできないけれど関連データを表示するときには使いたい、などがあります。
Deviseで論理削除するには?
Wikiに書いてある方法だと再登録不可能
Deviseは非常に便利なのですが、デフォルトでは論理削除に対応していないため、カスタマイズする必要があります。
一応、GitHubのリポジトリのWikiには、論理削除のやり方が書いてあります。
How to: Soft delete a user when user deletes account · heartcombo/devise Wiki · GitHub
それを翻訳してくださっている記事がQiitaにあります。
【翻訳】deviseで論理削除を実装する方法 - Qiita
ただし、この方法だと削除済みのメールアドレスで再登録しようとしたらできません。
そこで、今回は削除済みのメールアドレスで再登録可能な論理削除の実装方法をまとめておきます。
実装手順
deviseとkakurenbo-putiをインストールする。
Gemfileにdeviseとkakurenbo-putiを追加します。kakurenbo-putiは、論理削除機能を追加するためのgemです。論理削除機能を追加するgemは多々あるのですが、default scopeを上書きする等して論理削除済アカウントを除外するような動作をするものや、Railsのアップデートに伴って動作しなくなるものがあり、非常に辛いことになるのですが、kakurenbo-putiはdefault scopeを上書きせずにあくまでも機能を追加するだけなので副作用の心配がありません。
では、追加しましょう。
gem 'devise' gem 'devise-i18n' # <-手軽に日本語化したければ… gem 'kakurenbo-puti'
bundle install します。
deviseをセットアップする
deviseをセットアップします。今回はUserモデルとします。論理削除のみに集中するので、他の設定については公式を参照してください。
$ bin/rails generate devise:install $ bin/rails generate devise User
これで、deviseの各種ファイルとUserモデルのファイルとマイグレーションファイルが追加されました。まだマイグレーションしないでください。
マイグレーションファイルを修正する
deviseのデフォルトのマイグレーションだと、論理削除を考慮した構成になっていませんので、論理削除用のカラムの追加と、インデックスの設定の修正を行います。
具体的には、以下を追加します。
class DeviseCreateUsers < ActiveRecord::Migration[6.0] def change create_table :users do |t| # 略 # kakurenbo-puti t.datetime :soft_destroyed_at # 略 end # 元々あるemailをユニークにするindexはコメントアウトor削除する # add_index :users, :email, unique: true # 代わりに、soft_destoryed_atがNULLであることを条件にした部分indexを追加する add_index :users, :email, unique: true, where: '(soft_destroyed_at IS NULL)' add_index :users, :soft_destroyed_at end end
修正が終わりましたら、マイグレーションを実行します。
$ bin/rails db:migrate
kakurenbo-putiをセットアップする
Userモデルでkakurenbo-putiの機能が使えるようにするため、修正します。
メールアドレスで再登録可能な実装をする場合、deviseのvalidatableはemailのみでユニークであることを検証しようとするため、コメントアウトして、代わりに自分でvalidationを追加する必要があります。
class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, # :validatable, # <- emailのみでユニーク制約を検証してしまうのでコメントアウトする :confirmable, :lockable, :timeoutable soft_deletable # <- kakurenbo-putiを使えるようにする # 論理削除に対応するため、validationをカスタマイズする validates :email, presence: true, length: { maximum: 255 } validates_uniqueness_of :email, scope: :soft_destroyed_at validates_format_of :email, with: Devise.email_regexp, if: :will_save_change_to_email? validates :password, presence: true, confirmation: true, length: { in: Devise.password_length }, on: :create validates :password, confirmation: true, length: { in: Devise.password_length }, allow_blank: true, on: :update # 略 end
これで、Userモデルに論理削除用の機能が追加されました。
kakurenbo-putiで追加されるメソッド
論理削除機能が追加されることで、以下のようなメソッドが使えます。
ActiveRecord::Relation
User.all # 論理削除済ユーザも含んだ全てのユーザを取得 User.without_soft_destroyed # 論理削除済ユーザを除外して取得 User.only_soft_destroyed # 論理削除済ユーザのみ取得
Userモデル
user = User.without_soft_destroyed.first user.soft_destroyed? # => false user.soft_destroy # => 論理削除を実行 user.soft_destroyed? # => true user.restore # => 復元を実行 user.soft_destroyed? # => false
他にも、callbackや破壊的メソッドなどもありますので詳しくはkakurenbo-putiのページをご参照ください。
ログイン時の処理で論理削除済ユーザを除外する
あとは、実際にログイン処理を行うときに利用されるメソッドであるfind_for_database_authentication
を上書きします。
参考になる公式のWikiはこちら。こちらはユーザ名でログインの実装なのですが、やっていることはfind_for_database_authentication
の上書きです。
再び、Userモデルを修正します。やることは、without_soft_destroyed
を挟むことで、論理削除済ユーザを除外することです。
class User < ApplicationRecord # 略 # データベース認証時に使われるメソッドを上書きして、 # without_soft_destroyedを追加する def self.find_for_database_authentication(warden_conditions) conditions = warden_conditions.dup self.without_soft_destroyed.where(conditions.to_h).first end # 略 end
アカウント削除のactionを上書きする
あとは、アカウント削除処理を上書きしてdestroyする箇所をsoft_destroyにする必要があります。
まず、上書き用のコントローラーを作成しておきます。
$ rails generate devise:controllers users
これで、app/controllers/users
配下にコントローラーができました。まだこれは使われる状態ではありません。config/routes.rb
に設定する必要があります。
では、config/routes.rb
でdeviseのregistrationsのコントローラーに先ほど生成されたコントローラーを指定します。
Rails.application.routes.draw do # 略 devise_for :users, controllers: { registrations: 'users/registrations', } # 略 end
最後に、app/controllers/users/registrations_controller.rb
を編集します。
class Users::RegistrationsController < Devise::RegistrationsController # 略 # DELETE /resource def destroy resource.soft_destroy # <- 論理削除を実行 Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name) set_flash_message :notice, :destroyed yield resource if block_given? respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) } end # 略 end
まとめ
これで、deviseに論理削除を実装することができました。 deviseとkakurenbo-putiを合わせて使うと、論理削除の実装の手間がかなり軽減されます。 それでは、よい論理削除ライフを。