もふもふ技術部

IT技術系mofmofメディア

devise sign_in/sign_outのデフォルトルーティングをカスタマイズしてlogin/logoutにする

deviseのwikiにあるHow To: Change the default sign_in and sign_out routes を翻訳、実際に動かしてみる。

通常、deviseを実装した段階では、生成されるルーティングはdeviseがデフォルトで提供してくれるものになります。例えばuserモデルのログインなどであれば/users/sign_in/users/sign_outです。 もちろんこのままでも機能的には問題なく動作しますし、画面表示上の違いは無いのですが、わかりやすいURL構造にすることによって検索エンジンに有利に働いたり、ユーザーにわかりやすくなったりするというメリットもあったりします。

そういった場合に、deviseで対応するルーティングを任意のものに変える方法を解説していきます。

言語、ライブラリのバージョン

  • Ruby 2.6.3
  • Rails 6.0.2.2
  • devise 4.7.1
  • テンプレートはslimを使用

導入から基本的な実装を知りたい場合は最小構成導入からログアウトまでを参照。 deviseを使用しているモデルはUserモデルとしています。適宜自分の環境に読み替えてください。

また、今回出てくるコードは全てroutes.rbに記述するものになります。

devise_scopeをカスタマイズする方法

はじめに紹介する方法はdevise_scopeを使ってURLと対応するアクションを設定する方法です。

Rails.application.routes.draw do
  devise_scope :user do
    get 'login', to: 'devise/sessions#new'
  end
end

devise_scope :モデル名とすることでブロック内でカスタマイズしたルーティングの設定ができます。デバイスを使用しているモデルが複数ある場合は同じURLの構文は使えないのでnamespaceなどで切り分けましょう。

上記記述によって下記URLでloginページにアクセスできるようになります。

http://localhost:3000/login

また、devise_scopeはasとエイリアスされているので、下記構文も同様に動作します。 つまり、下記のように書いた場合、http://localhost:3000/loginでも、http://localhost:3000/login2でもログインページに遷移することができます。

Rails.application.routes.draw do
  devise_scope :user do
    get 'login', to: 'devise/sessions#new'
  end
  as :user do
    get 'login2', to: 'devise/sessions#new'
  end
end

sign_outの場合も同じように書けます。

Rails.application.routes.draw do
  devise_scope :user do
    delete 'logout', to: 'devise/sessions#destroy'
  end
end

特定のルーティングをカスタマイズする

skipオプションを利用することで、特定のデフォルトルーティングをスキップし、独自のルーティングをカスタマイズできます。例えば、sessionsに関するルーティングだけを特定のurlにしたい場合は下記のようにします。

Rails.application.routes.draw do
  devise_for :users, skip: [:sessions]
  as :user do
    get 'signin', to: 'devise/sessions#new', as: :new_user_session
    post 'signin', to: 'devise/sessions#create', as: :user_session
    delete 'signout', to: 'devise/sessions#destroy', as: :destroy_user_session
  end
end

最初のdevise_for :users, skip: [:sessions]でsessionsコントローラーのみルーティングをスキップします。

resources :user except: [:edit]

のような形式と似ていますね。

その上でカスタマイズしたルーティングを設定できます。上記例では、

http://localhost:3000/signin

でログインページに遷移できます。

この方法でカスタマイズを行った場合、deviseが提供するhelperメソッドの結果としてリダイレクトなどが行われた場合、自動的にこのURLに遷移されるようになります。

例えば、authenticate_user!などのメソッドによって未ログイン時には遷移できないページにアクセスしようとした場合、ログインページに遷移することになりますが、その際のurlはhttp://localhost:3000/signinになります。

sign_out_viaを設定している場合

この部分は本題から逸れてしまうので、必要なければ読み飛ばしてください。 ログアウト処理を行った場合、

No route matches [GET] "/users/sign_out"

のようなエラーに遭遇する場合があります。このエラーはdeviseのサインアウトのメソッドはdeleteになっているのに対して、getメソッドとしてアクセスしようとした場合におきます。 よくあるのはlink_toのオプションとしてmethod: :deleteを使用しているのにそれが効かなかったりした場合ですね。この問題を解消するにはrails_ujsなどを入れることで動的にアンカーリンクを生成することで対応するケースが多いですが、deviseでも、 config/initializers/devise.rbにあるconfig.sign_out_viaというオプションの値を:deleteから:getに変えてあげても対応できます。

Devise.setup do |config|
#中略
config.sign_out_via = :get
#中略
end

この対応を行った場合に、そのままだとデフォルトの挙動、つまりdeleteを見に行ってしまうので sign_out_viaを適用するように指定してあげる必要があります。

Rails.application.routes.draw do
 devise_for :users, skip: [:sessions]
 as :user do
  get 'signin', to: 'devise/sessions#new', as: :new_user_session
  post 'signin', to: 'devise/sessions#create', as: :user_session
  match 'signout', to: 'devise/sessions#destroy', as: :destroy_user_session, via: Devise.mappings[:user].sign_out_via
 end
end

簡単にカスタマイズしたい場合

上記ほどのカスタマイズはいらず、単純にsign_inをlogin, sign_outをlogoutにしたい場合、下記一行で簡単に変更することも可能です。

Rails.application.routes.draw do
  devise_for :users, path: '', path_names: { sign_in: 'login',   sign_out: 'logout'}
end

この記述を行うことで、

http://localhost:3000/login
http://localhost:3000/logput

のURLでそれぞれの該当ページに遷移、処理を実行することができます。

以上、deviseのログイン/ログアウトのルーティングカスタマイズの方法でした。 実務では何かと要望の多いこの辺りの実装は何度も調べてしまいがちです。参考になれば幸いです。