この記事では、devise gemを使ってRailsアプリケーションにユーザー認証機能を実装する方法を紹介します。
1. 実行環境
- macOS:12.5.1
- Ruby:3.1.2
- Rails:6.1.7
- devise:4.8.1
- devise-i18n:1.10.2
2. 手順
2-1. サンプルアプリの作成
以下のコマンドを実行して、サンプルアプリを作成します。
$ rails new sample_app
$ cd sample_app
$ rails generate scaffold book title:string memo:text
$ rails db:migrate
2-2. deviseのセットアップ
Gemfileに以下を追記して、bundle installを実行します。
gem 'devise'
アプリケーションのルートディレクトリで以下のコマンドを実行して、deviseをセットアップします。
$ rails generate devise:install
config/environments/development.rbに以下を追記して、デフォルトの url オプションを定義します。
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
2-3. ルートルーティングの設定
config/routes.rbに以下を追記して、ルートルーティングを設定します。
Rails.application.routes.draw do
root to: 'books#index'
end
deviseはサインインやパスワード変更などを行った後のデフォルトのリダイレクト先にルート(/)を使用します。
上記の設定により、リダイレクト先が/から/booksに変更されます。
2-4. フラッシュメッセージの表示
app/views/layouts/application.html.erbを以下のように編集します。
<%# 省略 %>
<body>
<% if notice.present? %>
<p id="notice"><%= notice %></p>
<% end %>
<% if alert.present? %>
<p id="alert"><%= alert %></p>
<% end %>
<%= yield %>
</body>
app/views/books/index.html.erbとapp/views/books/show.html.erbの以下の行を削除します。
<p id="notice"><%= notice %></p>
2-5. Userモデルの作成
以下のコマンドを実行して、Userモデルを作成します。
$ rails generate devise user
$ rails db:migrate
上記のコマンドにより、Devise がアカウントの作成,ログイン,ログアウトなどに関するすべてのコードやルーティングを生成します。
2-6. ログインしていない時に登録した内容を確認できないようにする
app/controllers/application_controller.rbに以下を追記します。
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
上記の設定により、ログインしていない時の各ページへのアクセスは全てログイン画面に遷移するようになります。
app/views/layouts/application.html.erbにアカウント編集とログアウトのリンクを追加します。
<body>
<% if user_signed_in? %>
<div class="menu-container">
<div class="title">Logged in as <%= current_user.email %></div>
<ul>
<li>
<%= link_to 'Edit profile', edit_user_registration_path %>
</li>
<li>
<%= link_to 'Logout', destroy_user_session_path, method: :delete %>
</li>
</ul>
</div>
<% end %>
<% if notice.present? %>
<p id="notice"><%= notice %></p>
<% end %>
<% if alert.present? %>
<p id="alert"><%= alert %></p>
<% end %>
<%= yield %>
</body>
2-7. ユーザー一覧画面とユーザー詳細画面の作成
以下のコマンドを実行して、Userモデルにnameとself_introductionカラムを追加します。
$ rails generate migration add_name_and_self_introduction_to_users name:string self_introduction:text
$ rails db:migrate
config/routes.rbに以下のルーティングを追記します。
resources :users, only: %i(index show)
以下のコマンドを実行して、users#index, users#showのルーティングが追加されたことを確認します。
$ rails routes
# 省略
users GET /users(.:format) users#index
user GET /users/:id(.:format) users#show
以下のコマンドを実行して、usersコントローラーを作成します。
$ rails generate controller users index show --skip-routes --no-helper --no-assets --no-test-framework
生成されたapp/controllers/users_controller.rbを以下のように編集します。
class UsersController < ApplicationController
def index
@users = User.all
end
def show
@user = User.find(params[:id])
end
end
次に、ユーザ一ー覧画面とユーザー詳細画面を編集します。
app/views/users/index.html.erbを以下のように編集します。
<h1>ユーザー</h1>
<table>
<thead>
<tr>
<th>Eメール</th>
<th>氏名</th>
<th></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= user.name %></td>
<td><%= link_to '詳細', user %></td>
</tr>
<% end %>
</tbody>
</table>
app/views/users/show.html.erbを以下のように編集します。
<h1>ユーザーの詳細</h1>
<p>
<strong>Eメール:</strong>
<%= @user.email %>
</p>
<p>
<strong>氏名:</strong>
<%= @user.name %>
</p>
<p>
<strong>自己紹介文:</strong>
<%= @user.self_introduction %>
</p>
<% if current_user == @user %>
<%= link_to '編集', edit_user_registration_path %> |
<% end %>
<%= link_to '戻る', users_path %>
app/views/layouts/application.html.erbにユーザー一覧画面へのリンクを追加します。
<%# 省略%>
<% if user_signed_in? %>
<div class="menu-container">
<div class="title">メニュー</div>
<ul>
<li>
<%= link_to '本', books_path %>
</li>
<li>
<%= link_to 'ユーザー', users_path %>
</li>
</ul>
<div class="title">Logged in as <%= current_user.email %></div>
<ul>
<li>
<%= link_to 'Edit profile', edit_user_registration_path %>
</li>
<li>
<%= link_to 'Logout', destroy_user_session_path, method: :delete %>
</li>
</ul>
</div>
<% end %>
<%# 省略%>
2-8. サインアップ画面とアカウント編集画面の編集
Gemfileに以下を追記し、bundle installを実行してdevise-i18nをインストールします。
gem 'devise-i18n'
以下のコマンドを実行して、devise:i18n:viewsを生成します。
$ rails g devise:i18n:views -v registrations
上記により、devise-i18n/app/views/devise/registrations/edit.html.erbのedit.html.erbとnew.html.erbが生成されます。
$ rails g devise:views -v registrationsの場合- Configuring views - devise
- devise/app/views/devise/registrationsの
edit.html.erbとnew.html.erbが生成される
app/views/devise/registrations/_profile_fields.html.erbを作成します。
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :self_introduction %>
<%= f.text_area :self_introduction %>
</div>
app/views/devise/registrations/new.html.erbを以下のように編集します。
<%# 省略 %>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<%# この1行を追加 %>
<%= render 'devise/registrations/profile_fields', f: f %>
<div class="field">
<%= f.label :password %>
<% if @minimum_password_length %>
<em><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></em>
<% end %><br />
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<%# 省略 %>
app/views/devise/registrations/edit.html.erbを以下のように編集します。
<%# 省略 %>
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div><%= t('.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %></div>
<% end %>
<%# この1行を追加 %>
<%= render 'devise/registrations/profile_fields', f: f %>
<div class="field">
<%= f.label :password %> <i>(<%= t('.leave_blank_if_you_don_t_want_to_change_it') %>)</i><br />
<%= f.password_field :password, autocomplete: "new-password" %>
<% if @minimum_password_length %>
<br />
<em><%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %></em>
<% end %>
</div>
<%# 省略 %>
次にストロングパラメータ(Strong Parameters)を追加します。
app/controllers/application_controller.rbに以下を追記します。
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
keys = %i[name self_introduction]
devise_parameter_sanitizer.permit(:sign_up, keys: keys)
devise_parameter_sanitizer.permit(:account_update, keys: keys)
end
end
上記により、サインアップ時 (Devise::RegistrationsController#create)とアカウント編集時(Devise::RegistrationsController#update)のみnameカラムとself_introductionカラムの更新を許可します。
- Strong Parameters - devise
devise_parameter_sanitizerdevise_controller?
2-9. i18nで日本語化
i18nの基本的な使い方は以下を参照ください。
はじめに、使用する言語のリストとデフォルトで使用する言語を設定します。
config/initializers/locale.rbを作成し、以下を追記します。
I18n.available_locales = [:en, :ja]
I18n.default_locale = :ja
次に、日本語のロケールファイルを作成します。
config/locales/ja.ymlを作成し、以下を追記します。
ja:
activerecord:
models:
book: 本
attributes:
user:
name: 氏名
self_introduction: 自己紹介文
views:
common:
show: 詳細
edit: 編集
back: 戻る
title_show: "%{name}の詳細"
sign_out: ログアウト
layouts:
application:
menu: メニュー
sign_in_as: "%{email} としてログイン中"
# devise-i18n-viewsをオーバーライド
devise:
registrations:
edit:
title: "アカウント編集"
- devise-i18n gemのインストールにより、devise-i18n/rails/locales/ja.ymlの翻訳も反映されます。
最後に、各viewページに翻訳を反映させていきます。
app/views/layouts/application.html.erbを以下のように編集します。
<!DOCTYPE html>
<html>
<head>
<title>SampleApp</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
<% if user_signed_in? %>
<div class="menu-container">
<div class="title"><%= t('.menu') %></div>
<ul>
<li>
<%= link_to Book.model_name.human, books_path %>
</li>
<li>
<%= link_to User.model_name.human, users_path %>
</li>
</ul>
<div class="title"><%= t('.sign_in_as', email: current_user.email) %></div>
<ul>
<li>
<%= link_to t('devise.registrations.edit.title'), edit_user_registration_path %>
</li>
<li>
<%= link_to t('views.common.sign_out'), destroy_user_session_path, method: :delete %>
</li>
</ul>
</div>
<% end %>
<% if notice.present? %>
<p id="notice"><%= notice %></p>
<% end %>
<% if alert.present? %>
<p id="alert"><%= alert %></p>
<% end %>
<%= yield %>
</body>
</html>
app/views/users/index.html.erbを以下のように編集します。
<h1><%= User.model_name.human %></h1>
<table>
<thead>
<tr>
<th><%= User.human_attribute_name(:email) %></th>
<th><%= User.human_attribute_name(:name) %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= user.name %></td>
<td><%= link_to t('views.common.show'), user %></td>
</tr>
<% end %>
</tbody>
</table>
app/views/users/show.html.erbを以下のように編集します。
<h1><%= t('views.common.title_show', name: User.model_name.human) %></h1>
<p>
<strong><%= User.human_attribute_name(:email) %>:</strong>
<%= @user.email %>
</p>
<p>
<strong><%= User.human_attribute_name(:name) %>:</strong>
<%= @user.name %>
</p>
<p>
<strong><%= User.human_attribute_name(:self_introduction) %>:</strong>
<%= @user.self_introduction %>
</p>
<% if current_user == @user %>
<%= link_to t('views.common.edit'), edit_user_registration_path %> |
<% end %>
<%= link_to t('views.common.back'), users_path %>
2-10. サインイン,サインアウト,アカウント編集後のリダイレクト先の変更
app/controllers/application_controller.rbに以下を追記します。
class ApplicationController < ActionController::Base
# 省略
private
def after_sign_in_path_for(resource_or_scope)
books_path
end
def after_sign_out_path_for(resource_or_scope)
new_user_session_path
end
def signed_in_root_path(resource_or_scope)
user_path(current_user)
end
end
上記では、deviseに実装されているafter_sign_in_path_forメソッド、after_sign_out_path_forメソッド、signed_in_root_pathメソッドをオーバーライドしています。
これにより、サインイン後に/books、サインアウト後に/users/sign_in、アカウント編集後に/users/:idにリダイレクトします。
- Controller filters and helpers - devise
after_sign_in_path_forafter_sign_out_path_forsigned_in_root_path
2-11. letter_opener_webで送信メールをブラウザ上で確認できるようにする
【Rails】letter_opener_webで送信メールをブラウザ上で確認する - あまブログ
【参考】
- Devise で作成した User モデル用のコントローラーの index, show アクションを追加 | EasyRamble
- How To: Change the redirect path after destroying a session i.e. signing out · heartcombo/devise Wiki
- How To: Customize the redirect after a user edits their profile · heartcombo/devise Wiki
- Rails: Devise: redirect after sign in by role - Stack Overflow