【翻訳】deviseで論理削除を実装する方法


deviseで論理削除を実装する必要があり、deviseのWikiはあるけど日本語の記事が見当たらなかったので当該記事を翻訳することにしました。

参考:How to: Soft delete a user when user deletes account

物理削除と論理削除の違い

普通にDBからデータを削除することを物理削除と言います。一方で、DBにフラグとなるフィールドを作成し、削除時に削除フラグを立てることにより仮想的に削除時に見えなくする処理のこと。
deviseのデフォルトの退会機能は物理削除だが、例えば退会後もユーザー情報を保持したい場合は論理削除を実装する必要があります。(というか、多分そういう場合がほとんどではないかな)

以下、訳文。

ユーザーアカウントを削除するがユーザー情報は保持する(論理削除)

ユーザーがアカウントの削除を選択すると、すべてのデータが工夫によって破壊されます。
ユーザーデータは保持する一方で、ユーザーを非アクティブにし、ログインできないようにすることが求められる場合があります。
以下はユーザーを論理削除する方法です。

  1. datetime型のdeleted_atカラムを追加
  2. routingのusers/registrations#destroyをオーバーライド
  3. registrations controllerのusers/registrations#destroyをオーバーライド
  4. 論理削除でユーザーモデルを更新し。認証でユーザーがアクティブかどうかを確認する
  5. delete時のカスタムメッセージを追加

1. Usersモデルにdeleted_atを追加

下記をターミナルで実行

rails g migration AddDeletedAtColumnToUsers deleted_at:datetime
rake db:migrate

2. config/routes.rbで削除時のroutingをオーバーライド


# devise_for :usersの部分を下記のように修正
devise_for :users, :controllers => { :registrations => 'users/registrations' }

3. registrations controllerのusers/registrations#destroyをオーバーライド

まだ作成していなければ、app/controllers/users/registrations_controller.rbを作成する。
ここでDevise registrations controllerを継承し、destroyをオーバーライドする。

# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  # DELETE /resource
  def destroy
    resource.soft_delete
    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

4. 論理削除でユーザーモデルを更新し。認証でユーザーがアクティブかどうかを確認する

  # app/models/user.rb  

  # 物理削除の代わりにユーザーの`deleted_at`をタイムスタンプで更新
  def soft_delete  
    update_attribute(:deleted_at, Time.current)  
  end

  # ユーザーのアカウントが有効であることを確認 
  def active_for_authentication?  
    super && !deleted_at  
  end  

  # 削除したユーザーにカスタムメッセージを追加します  
  def inactive_message   
    !deleted_at ? super : :deleted_account  
  end 

5. 削除されたアカウントでログインしようとしたときのカスタムエラーメッセージを設定

# config/locales/*your-filename-here*.yml

en:  
  devise:  
    failure:  
      deleted_account: "You've deleted your account. Thanks!" 

ja:
  devise:  
    failure:  
      deleted_account: "このアカウントは既に削除されています。" 

終わりに

以上で終了です。結構簡単に実装できますね。
でも論理削除だけだと、「削除したアカウントがまた同じメールアドレスで登録しようとするときはどうするの?」となってしまうので、その方法が無いか調べましたが見つかりませんでした。
多分同じようにコントローラーを継承してオーバーライドしていくんでしょうが、confirmationなどもあると厄介ですね。
もしご存知の方が居れば良い方法を教えて頂けるとありがたいです。