Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails: Deviseを徹底理解する(1)基礎編(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails: Deviseを徹底理解する(1)基礎編(翻訳)

原注

本記事は、Rubyの隠れたgem: Deviseシリーズ記事の一部です。

  1. Rails: Deviseを徹底理解する(1)基礎編(翻訳) -- 本記事
  2. Rails: Deviseを徹底理解する(2)応用編

Devise gemは、Rubyの世界で広く用いられているgemの1つで、GitHubの★が20,000個を超え、多くの統合も行われています。今になってDeviseをRubyの「隠れた」gemと呼ぶ理由は何なのでしょうか?実は、Deviseの人気は非常に高いにもかかわらず、多くの開発者がこのライブラリの機能を十分に活用していないからです。

2部構成の本シリーズでは、Deviseを深く掘り下げていきます。

このパート1記事では、以下を含む基礎をある程度学びます。

  • Deviseとは何か、Deviseを最初に使うべき理由、Deviseを使わない方がよい場合
  • Deviseのインストール方法、およびプロジェクトでの利用方法
  • プロジェクトに合わせてDeviseライブラリをカスタマイズする方法

次回パート2記事では、以下を含むDeviseの高度な利用法を見ていきます。

  • OmniAuth、およびAPI専用アプリケーションでのDeviseの使い方
  • Deviseを認証ライブラリと統合する

それでは始めましょう!

🔗 前提条件

このチュートリアルでは、ユーザー機能やタスク機能を備えたシンプルなRuby on Rails 7アプリケーションを使います。
ユーザーは登録、ログイン、ログアウトが可能で、ユーザーに割り当てられたロール(役割)に応じてタスクのcreatereadupdatedelete操作を実行できます。

このアプリを使って、複雑な機能を徐々に構築しながら、Deviseの強力な機能を実際に活用する様子をお見せします。

まずはDeviseを手短に紹介します。

🔗 Devise gemとは

Deviseは、Rackベースの認証フレームワークであるWarden gemを基にした認証(authentication)ライブラリです。

heartcombo/devise - GitHub

wardencommunity/warden - GitHub

Wardenは、ログイン済みユーザーの身元をセキュアなセッション文字列で確認するために、ユーザーセッションを処理します。
また、ログインしていないユーザーが、制限されたリソースにアクセスできないようにする処理も行います。

Wardenは純粋にRackベースのライブラリなので、コントローラアクションや、ビュー、ヘルパーなど、適切なユーザー認証ソリューションを構築するために必要な設定オプションを追加しません。一方、Deviseはそうしたものを追加します。

Deviseのもう1つの大きな特徴は、モジュール性です。
このライブラリにはモジュールが10個ほど付属しており、アプリケーションで認証をどのように扱いたいかを正確に指定できます。
必ずしも10個のモジュールをすべて使う必要はなく、アプリケーションに必要なものだけを有効化して利用します。
これらのモジュール(Registerableモジュール、OmniauthableTrackableなど)について詳しくは後述します。

以上を念頭に置いて、Tasksアプリの構築とDeviseのインストールを始めることにしましょう。

🔗 Deviseをインストールする

bundle exec rails new tasks_appを実行して、Rails 7アプリを新規に生成します1。または以下のサンプルアプリのコードをリポジトリから取得します2

iamaestimo/tasker_app - GitHub

アプリのrootディレクトリにいることを確認してから、bundle add devise を実行します。これにより、Devise gem がアプリの Gemfile に追加されます。
続いて、bundle exec rails g devise:install を実行して Devise をインストールし、初期化ファイル(config/initializers/devise.rb)を生成します(Devise のモジュールを詳しく見るにはこのファイルを見てみましょう)。

なお、このコマンドを実行すると、Deviseが正常に動作するためのいくつかのセットアップの提案がジェネレータによって表示されます。この提案に沿って進めておけばOKです。

▶参考: Deviseインストールメッセージ(クリックすると表示されます)

Depending on your application's configuration some manual setup may be required:

1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

In production, :host should be set to the actual host of your application.

Required for all applications.

2. Ensure you have defined root_url to something in your config/routes.rb.

For example:

root to: "home#index"

Not required for API-only Applications

3. Ensure you have flash messages in app/views/layouts/application.html.erb.

For example:

<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

Not required for API-only Applications

4. You can copy Devise views (for customization) to your app by running:

rails g devise:views

Not required

次に、Deviseが連携するユーザーモデルを生成しましょう。

🔗 Deviseのユーザーモデルを生成する

Deviseでは何らかのユーザーモデルが必要です。このモデルにどんな名前を付けるかは、完全に自分の好みとアプリケーションの利用方法次第ですが、UserまたはAdminにするのが普通です。

bundle exec rails g devise Userを実行して、Deviseのユーザーモデルを生成します。これにより、app/models/user.rbの下にUserモデルが生成され、usersテーブル作成用のマイグレーションファイルと、ユーザーリソースへアクセスするためのルーティングも生成されます。

# app/config/routes.rb

Rails.application.routes.draw do
  devise_for :users

  root "home#index"
end

bundle exec rails db:migrateを実行して、マイグレーションを実行し、usersテーブルをセットアップします。これにより、認証に関するすべての操作で利用可能なUserモデルが作成されます。

次に、今後利用するTaskモデルをセットアップします。

🔗 Taskモデルを生成する

私たちのシンプルなtasks_appでDeviseを使う場合の要件は以下になります。

  • ユーザーが登録を行えるようにする
  • ユーザーがアプリにサインイン/サインアウトできるようにする
  • ユーザーが、自分が属しているTaskを作成できるようにする
  • ユーザーのロールに応じて、Taksにアクセスを許可(または禁止)する

以上を念頭に置いて、ユーザーがやりとりするTaskモデルを生成します。

bundle exec rails g scaffold Task user:references title body:text status:integer

これで、作成したユーザーにTaskモデルを関連付けできるようになり、titlebody、およびstatus(タスクが完了しているかどうかを示す)属性もTaskモデルに追加されたはずです。

終わったら、bundle exec rails db:migrateを実行してtasksテーブルを作成します。このとき、以下のようにTaskモデルとUserモデルの関連付けも行ってください。

# app/models/user.rb

class  User  <  ApplicationRecord
  devise :database_authenticatable, :registerable,
  :recoverable, :rememberable, :validatable

  has_many :tasks, dependent:  :destroy  #この行を追加
end

scaffoldジェネレータを実行すれば、belongs_to :users関連付けが自動的にTaskモデルに追加されます。そのうえで、以下のようにTaskモデルを編集してstatusenumを使うようにします。

# app/models/task.rb

class Task < ApplicationRecord
  belongs_to :user

  enum :status, { draft: 0, underway: 1, done: 2, archived: 3 }
end

それでは、いよいよDeviseについて詳しく掘り下げていきましょう。最初に、Deviseのモジュールの概要を手短に押さえておきます。

🔗 Deviseのモジュール

本記事冒頭で述べたように、Deviseの主な特徴の1つはモジュール性です。しかし、この「モジュール性」は実際には何を意味するのでしょうか?簡単に言えば、認証プロセスを分割して独立して管理するという意味です。

本記事執筆時点では、Deviseに以下の10個のモジュールがあります。

DatabaseAuthenticatable
このモジュールは、ユーザーが提供したパスワードを安全なハッシュに変換してデータベースに保存します。また、ユーザーがサインインする際のバリデーションも行います。
Omniauthable
OmniAuth認証を有効にします。
Lockable
このモジュールは、ログイン失敗の回数に応じてアカウントをロックします。アカウントは、一定の時間経過後またはメール経由で再び利用可能となります。
Trackable
アカウントのログイン回数、使用されたIPアドレス、ログインのタイムスタンプをトラッキングします。
Confirmable
アカウントが登録されると、確認手順をメールで送信します。ユーザーのログイン時にもアカウントが確認済みかどうかをチェックします。
Registerable
ユーザーがアプリにアカウントを登録できるようにし、ユーザーによるアカウントの編集や削除も処理します。
Recoverable
パスワードのリセットとアカウントの復旧を処理します。
Timeoutable
指定の時間が経過した後、ユーザーのセッションを無効にします。
Validatable
アカウント作成時にユーザーが提供するメールアドレスやパスワードをカスタムバリデーションするルールを定義できます。
Rememberable
ユーザーの認証中に、ユーザーをcookieで記憶します(ログインを保存)。

モジュールについて詳しくは、Deviseモジュールのドキュメントが有用です。

次は、Deviseのヘルパーやフィルタについてです。

🔗 Deviseのヘルパーとフィルタ

Deviseなどの認証ライブラリを使う理由の1つは、コントローラのリソースや、それに関連付けられるビューへのアクセスを管理するためです。Deviseには以下のような便利なヘルパーが用意されています3

user_signed_in?
現在ログイン中のユーザーがいるかどうかを確認します。
current_user
現在ログイン中のユーザーを参照できます。たとえば、current_user.emailのようなコードスニペットを使って、現在ログイン中のユーザーのメールアドレスを取得できます。
user_session
現在ログイン中のユーザーセッションです。
destroy_user_session_path
ログイン中のユーザーセッションを破棄し、指定されたパスまたはrootパスにリダイレクトします。
new_user_session_path
ユーザーログイン画面を表示します。
edit_user_registration_path
現在ログイン中のユーザーに、登録の詳細を編集するビューへのアクセス権限を与えます。
new_user_registration_path
新規ユーザーの登録フォームを持つビューを表示します。

ヘルパーについては以上です。

Deviseは、コントローラのアクセスを管理する便利なbefore_actionフィルタを提供しています。フィルタは以下のように利用可能です。

# app/controllers/posts_controller.rb

class  PostsController  <  ApplicationController

  before_action :authenticate_user!    # モデル名がUserの場合
  before_action :authenticate_member!  # モデル名がMemberの場合

  ...
end

次は、DeviseがRailsのstrong parametersとどのように統合されるかについて解説します。

🔗 DeviseとRailsの"Strong Parameters"について

strong parametersは、Ruby on Railsのよく知られた機能であり、リクエストパラメータをオブジェクトに一括で代入する「マスアサインメント(mass assignment)」を防止します。strong parametersでは、通常、コントローラレベルで明示的にリクエストパラメータを宣言しなければなりません。

参考: §4.5 Strong Parameters -- Action Controller の概要 - Railsガイド

Deviseも、Devise固有の適切なコントローラアクションの下で明示的なパラメータ定義を必須にすることで、strong parametersの機能を反映しています。

sign_up
このメソッドは、デフォルトではDeviseコントローラDevise::RegistrationsController#createで見つかります。デフォルトで許可されるキーは、emailpasswordpassword_confirmationです。
sign_in
このメソッドは、コントローラDevise::SessionsController#newで見つかります。デフォルトで許可される認証キーはemailpasswordです。
account_update
このメソッドは、コントローラDevise::Registrations#update内で見つかります。許可される認証キーは、emailcurrent_passwordpasswordpassword_confirmationです。

独自の認証キーを追加する場合に最も便利なのは、ApplicationController内でbefore_actionフィルタを使う方法です。
以下の例では、ユーザーがサインアップする際に必要なusernameキーを追加しています。

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
  end
end

Deviseのstrong parametersの興味深いユースケースは、Deviseサニタイザにキーの配列を渡す必要がある場合です。
たとえば、フリーランスのマーケットプレイスアプリがあるとします。このアプリにおけるユーザーは、以下のいずれかだとします。

  • 雇用可能な「契約者(contractor)」
  • 他の契約者を雇用できる「雇用主(employer)」
  • 契約者であり、雇用主でもある

ここでは技術的な詳細には触れませんが、この機能は、ユーザーが自分のロールをセレクトボックスの「契約者」と「雇用主」の2つから選んでアカウントを更新可能にすることで実現できます。こうすることで、ユーザーは「契約者」と「雇用主」のいずれか一方、または両方を選択できるようになります。

これを手軽に実現する方法は、ロールをキーとした配列を利用することですが、Railsのstrong parametersで許可されるのは以下のスカラー値のみです。

  • String
  • Symbol
  • NilClass
  • Numeric
  • TrueClass
  • FalseClass
  • Date
  • Time
  • DateTime
  • StringIO
  • IO
  • ActionDispatch::Http::UploadedFile
  • Rack::Test::UploadedFile

見ての通り、デフォルトでは配列やハッシュなどのオブジェクトは許可されていません。

ユーザーに複数のロールを許可する配列を使えるようにするには、コードを以下のように修正します。

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:account_update) do |user_params|
      user_params.permit({ roles: [] },
      :email, :password, :password_confirmation, :current_password)
    end
  end
end

このトピックについて詳しくは、Deviseヘルパーのドキュメントで解説されています。

次は、Deviseのビューやコントローラのカスタマイズ方法を学びます。

🔗 Deviseのビューをカスタマイズする

DeviseはRailsエンジンとして構築されているので、ほとんどのコンポーネントが事前にパッケージ化されています。ログイン用ビュー、サインアップ用ビュー、アカウント詳細の更新用ビュー、パスワードリセット用ビューなどはその良い例です。
アプリを構築するときに、これらの組み込みビューをカスタマイズする必要が生じることがあります。

最初のステップは、bundle exec rails g devise:viewsコマンドを実行してビューを生成します。これで、対応するビューが生成されてapp/views/deviseフォルダに配置されます。

Devise folder structure

Deviseのビューをカスタマイズする実用的な例を使ってみましょう。前のセクションでは、Deviseサニタイザに追加の認証キーを追加する方法を学びました。この例を拡張して、ユーザー登録ビューにusernameフィールドを追加しましょう。

usernameフィールドはデフォルトのUserモデルでは利用できないので、bundle exec rails g migration add_column_username_to_user usernameマイグレーションを実行してusernameフィールドを追加し、bundle exec rails db:migrateコマンドを実行してマイグレーションを開始し、usersテーブルにカラムを追加します。

次に、新しいユーザー登録用ビューファイルを開いて、以下のように編集します。

# app/devise/registrations/new.html.erb

<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  ...

  <!-- usernameフィールドを追加する -->

  <div class="field">
    <%= f.label :username %><br />
    <%= f.text_field :username, autofocus: true %>
  </div>
  ...
<% end %>

この方法で生成したDeviseのビューは、すべてのビューで使う分には問題ありません。しかし、特定のビューだけをカスタマイズしたい場合はどうすればよいでしょうか?
これは、bundle exec rails g devise:views -v sessions registrationsのような形式で、カスタマイズしたいビューのリストをジェネレータコマンドの-vフラグに渡すだけでできます。この場合、ログイン/ログアウトプロセスに関連するビューと、サインアップ処理を担当するビューが生成されます。

Deviseのカスタマイズについて先に進みましょう。今度は、Deviseのコントローラやルーティングのカスタマイズ方法を学びます。

🔗 Deviseのコントローラやルーティングをカスタマイズする

Deviseのビューのカスタマイズでできることには限界があります。本格的にカスタマイズをしたい場合は、Deviseのコントローラとルーティングに手をつける必要があります。

アプリで新しいユーザー登録が発生したときに管理者にメールで通知することを考えましょう(この方法は理想的とは言えませんが、そういう要望があった場合を説明する例として進めることにしましょう)。

まず、Devise::RegistrationsControllersignupアクションを変更する必要があります。通常、Deviseのコントローラは直接編集できませんが、ビューと同様にジェネレータで生成できます。

bundle exec rails generate devise:controllers users

上のコマンドの末尾はスコープ(この場合users)です。別のスコープにしたい場合は、それに応じて変更可能です。

コマンド実行が完了すると、生成されたコントローラは次のような構造になっているはずです。

Devise controllers folder structure

次には、routes.rbファイルを開き、Deviseのルーティングをコントローラ構造の変更に合わせて以下のように修正します。

# config/routes.rb

Rails.application.routes.draw do
  resources :tasks

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

  root "tasks#index"
end

新しく生成したUsers::RegistrationsControllerを開き、新規ユーザーがサインアップするたびに管理者にメールを送信するように変更します。

# app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
  ...

  def create
    super do |resource|
    # 管理者へのメール送信に何らかのカスタムメーラーを使う
    AdminNotifierMailer.user_signup_notification(resource).deliver_later
  end

  ...
end

このような方法でDeviseのコントローラとアクションにアクセスできるので、アプリケーションの認証フローを自由に変更できるようになります。

次回予告: Rails: Deviseを徹底理解する(2)応用編

この2回シリーズの記事のパート1基礎編では、Devise gemの基本について解説し、Deviseのさまざまなモジュールやビューやコントローラをカスタマイズする方法を学びました。

次回のパート2では、API認証や、OmniAuthをDeviseと併用する方法など、さらに高度な使い方について詳しく解説します。

それでは次回お会いするまで、happy coding!

P.S. Ruby Magicの記事をいち早くお読みになりたいのであれば、ぜひRuby Magicニュースレターに登録して、記事を見逃さずに読めるようにしましょう!

関連記事

Rails: Deviseを徹底理解する(2)応用編(翻訳)

Devise::SessionsControllerのcreateアクションで認証を回避する際の注意点


  1. 訳注: 本記事のDeviseインストール手順では、Railsセットアップの一部が省略されています。詳しくは§3.1 Railsのインストール -- Rails をはじめよう - Railsガイドなどをご覧ください。 
  2. 訳注: このサンプルアプリは、UserモデルやTaskモデルのセットアップまで完了しています。 
  3. 訳注: これらのヘルパーは、モデル名がUserの場合に動的に生成されるメソッドです(HelpersUrlHelpers)。モデル名が異なるとメソッド名も変わります。 

CONTACT

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