Tech Racho エンジニアの「?」を「!」に。
  • 開発

[Devise How-To] sign_inとsign_outのデフォルトルーティングを変更する(翻訳)

こんにちは、hachi8833です。
Devise gemのWiki How-To翻訳、第2弾です。DeviseもRails 5.1対応で忙しそうですね。

Devise Wikiもくじリンク

  1. 「ワークフローのカスタマイズ」
  2. 「認証方法のカスタマイズ」「OmniAuth」
  3. 「ビュー/コンテンツのカスタマイズ」「特権/認証」
  4. 「テスト」「特殊な設定」
  5. 「アプリでのその他の設定」「JavaScript」
  6. 「他の認証プラグインからの移行」「アップグレード」

概要

原文が更新されていることにお気づきの場合は、ぜひ@techrachoまでお知らせください。更新いたします。

[How-To] sign_inとsign_outのデフォルトルーティングを変更する

Deviseで使うロール(役割)が1つしかない状態で、ログイン(sign in)とログアウト用(sign out)のルーティングをデフォルトの /users/sign_in/users/sign_outから/login/logoutに変更したいことがあります。

これはデフォルトのままのDeviseではできません。Deviseは、URLをチェックすることによってユーザーのアクセスするスコープを決定するからです。URLが/users/loginであればスコープはuserであることがDevise側でわかりますが、/loginにアクセスすると、使われるべきスコープをDevise側で決定できません。幸いなことに、Deviseにはデフォルトスコープを指定するしくみがあるので、これによってURLを短くできます。

手順(Rails 3.0以降)

この設定は、アクセスに使うURLをルーティングのdevise_scopeに記述するだけで完了します。

devise_scope :user do
  get 'login', to: 'devise/sessions#new'
end

以下のように、devise_scopeのエイリアスであるasも使えます。

as :user do
  get 'login', to: 'devise/sessions#new'
end

sign_outの場合は次のようにします。

devise_scope :user do
  delete 'logout', to: 'devise/sessions#destroy'
end

次のようにskip:オプションですべてのセッションルーティングをスキップし、必要なルーティングだけを定義することもできます。

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

このようにすると、#authenticate_user!などのヘルパーを使ったときに、定義済みの正しいカスタムページにユーザーをリダイレクトできます。

ただし、:sign_out_viaという設定オプションを使っていると上のsignoutアクションがエラーになることがあります。その場合は、デフォルトの動作を複製して次のように:sign_out_viaによってdeletegetに変更します。

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

別のもっとシンプルな方法

#devise_forメソッドにはこういうときに便利なオプションパラメータが多数用意されています。

たとえば、usersという名前空間をすべてのルーティングから取り除き、sign_insign_outをそれぞれloginlogoutに置き換えるには、次のようにします。

devise_for :users, path: '', path_names: { sign_in: 'login', sign_out: 'logout'}

ただし、Deviseで複数のコントローラ(パスワード用など)を使いたい場合は、最初の方法を使う必要があります。

morimorihogeより追記

上記の sign_out_via に関連してすこし補足です。

Deviseではログインセッションをsessionという概念で管理しており、RESTful的には「新規sessionを作る(Devise::SessionsController#create)=ログインする」といった抽象化がされています。
そのため「ログアウトする」はsessionを破棄する、すなわちsessionの削除(Devise::SessionsController#destroy)というインターフェースになっています。

このとき、確かにsession削除はDELETEメソッドで呼ばれるのがRESTの概念上あるべき姿なのですが「ログアウトする」という機能自体はRailsの外から呼びたかったり、リダイレクト処理の中で強制ログアウトさせたいといったケースが考えられるため、DELETEメソッドよりもGETメソッドでログアウトできた方が便利です( _method=delete とか付ければGETでもDELETEメソッドにbypassできますが、いちいち考えるのが面倒)。

というわけで、以下の様に sign_out_via を設定してやることで、ログアウトについてはGETリクエストで処理でき、 link_to ヘルパでも単なる destroy_user_session_path へのリンクとして記述できるようになります。

Devise.config.sign_out_via = :get
= link_to 'ログアウト', destroy_user_path

関連記事(Devise)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。