DeviseでSign upはさせないけど、ユーザ自身にパスワード変更はさせたい

今日もDevise

READMEにも書いてある通り、Deviseは機能が複数のmoduleに分かれていて、有効にしたいmoduleを選んでいくわけだが、今回は Registerable について。

Registerable を有効にすると、アカウント未所持ユーザが自ら、自分のメールアドレスを入力してアカウント作成をすることができるようになる。いわゆる「サインアップ」という機能を提供してくれる。それと同時に、アカウント保持者向けに、自身のアカウントの削除や、パスワード変更の機能もある。

という便利なmoduleであるところまでは良いのだが、問題は、「サインアップや自身の削除は機能として提供したくないが、自身のパスワード変更機能は提供したい」という場合だ。この場合は、

  1. Registerable moduleのうち不要な機能を潰す
  2. Registerable moduleは使わずに、必要な機能のみを自前実装する

の2択を迫られる。今回は1の方法を取ることにする。

やりたいこと

Devise::RegistrationsController は以下のようなactionを持っている。

  • サインアップ用action
    • #new
    • #create
    • #cancel
  • パスワード変更用action
    • #edit
    • #update
  • アカウント削除用action
    • #destroy

この内、必要ない機能に紐づくactionを無効にするというのが、今回やりたいことになる。

本当はこうやってやりたかったけど見つからなかったパターン

本当は、routesの時点で、不要な機能に紐づくactionを全部無効にしたかった。ところが、routes用DSLである devise_for には、action単位で無効にするオプションは存在しなかった。Controller単位の skip はあるが、actionの指定はできなかった。

Controllerを上書きして潰す

Deviseは、「提供するControllerを継承してカスタマイズされたControllerを作る」という使い方はもともと想定されているので、これに乗る。

まずroutesに、自前Controllerを使うよう指示

devise_for :users, controllers: { registrations: 'users/registrations' }

そして、自前Controllerを実装。

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :guard_signup!, only: %i[cancel new destroy create]

  private

  def guard_signup!
    raise ActionController::RoutingError, 'NOT FOUND'
  end
end

このように、不要なactionに対して、before_actionで ActionController::RoutingError をraiseしてやる。

これで、サインアップURLにアクセスしても404になる。