あまブログ

ドキドキ......ドキドキ2択クイ〜〜〜〜〜〜〜ズ!!

【Rails】deviseでユーザー認証機能を実装する

この記事では、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.erbapp/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モデルにnameself_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.erbedit.html.erbnew.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カラムの更新を許可します。

2-9. i18nで日本語化

i18nの基本的な使い方は以下を参照ください。

amablog.tech

はじめに、使用する言語のリストとデフォルトで使用する言語を設定します。

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: "アカウント編集"

最後に、各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にリダイレクトします。


【参考】