【Rails】furimaアプリ ユーザー管理機能の実装(前編)

実装概要

  1. Deviseの導入
  2. 新規登録/ログイン/ログアウトができるまで実装
  3. モデルの単体テスト

1. Deviseの導入

Gemfile

gem 'devise'

ターミナル

# Gemをインストール
% bundle install

# サーバーを起動
% rails s

⚠️Gemインストール後、サーバー再起動忘れがち。

ターミナル

# deviseの設定ファイルを作成
% rails g devise:install

# deviseコマンドでUserモデルを作成
% rails g devise User

ここでマイグレーションを実行してもいいのですが、ロールバックが面倒臭いので、カラム追加するまでは一旦保留。

2. 新規登録/ログイン/ログアウトができるまで実装

💡UsersテーブルのカラムやUserモデルのバリデーションについては、前記事「DB設計」参照。

実装条件に従って、Usersテーブルにカラムを追加。
db/migrate/20XXXXXXXXXXXX_devise_create_users.rb

# 省略
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :nickname,           null: false
      t.string :family_name,        null: false
      t.string :first_name,         null: false
      t.string :family_name_kana,   null: false
      t.string :first_name_kana,    null: false
      t.date   :birth_day,          null: false
# 省略
  end
end

続いてバリデーションの設定。
正規表現で全角かな/カナ漢字のバリデーション、パスワードのバリデーションを設定しています。

この段階で他のモデルは作成していないので、アソシエーションはまだ記載してません。

models/user.rb

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

  validates :nickname,            presence: true
  validates :password,            format: { with: /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i }
  validates :family_name,         presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々ー]+\z/ }
  validates :first_name,          presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々ー]+\z/ }
  validates :family_name_kana,    presence: true, format: { with: /\A[ァ-ヶー]+\z/ }
  validates :first_name_kana,     presence: true, format: { with: /\A[ァ-ヶー]+\z/ }
  validates :birth_day,           presence: true
end

ここまで記載してから、マイグレーションとサーバーの再起動を実行しました。

ターミナル

# マイグレーションを実行
% rails db:migrate

# ローカルサーバーを起動
% rails s

ストロングパラメーターを使えるようにする

deviseに関しても、同様にストロングパラメーターをコントローラーに記述したいところ。
しかし、deviseの処理を行うコントローラーはGem内に記述されているため、編集することができません。

そのため、「devise_parameter_sanitizer」というメソッドを使用して、deviseのUserモデルに関わる「ログイン」「新規登録」などのリクエストからパラメーターを取得させます。
どこに書くの?って疑問についてですが、
コントローラーが継承しているファイル、「application_controller.rb」に記載します。 ここに処理を記述しておくことで、すべてのコントローラーで共通となる処理を作ることができます。

configure_permitted_parametersメソッドのメソッド名について、
deviseの提供元が新たに定義するメソッド名をこの名前で提供していことから、習慣的にこのメソッド名で定義することが多いらしい。

app/controllers/application_controller.rb

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

  def index
  end

  private

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up,
                                      keys: [:nickname, :family_name, :first_name, :family_name_kana, :first_name_kana,
                                             :birth_day])
  end
end

deviseのビューファイルを作成

deviseでは、認証周りが実装されたビューファイルを一行のコマンドで作成することができます。

ターミナル

% rails g devise:views

サインアップ画面は「app/views/devise/registrations/new.html.erb 」

ログイン画面のビューは「app/views/devise/sessions/new.html.erb 」

というビューファイルが対応しているので、それぞれ編集。

ここで思ったことが1つ。
users controllerのアクションで@userを定義していないのに、なぜform_withで@userが使用できるのか。

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

<%= form_with model: @user, url: user_registration_path, class: 'registration-main', local: true do |f| %>
# 省略
<%= end %>

app/views/devise/sessions/new.html.erb

<%= form_with model: @user, url: user_session_path(@user), class: 'registration-main', local: true do |f| %>
# 省略
<%= end %>

devise が内部的にコントローラーとビューの間で @user を提供する仕組みを持っているためらしい。
users コントローラーが明示的に定義されていなくても、devise は必要なアクションやメソッドを提供してくれるそう。
深掘りするとこんがらがりそうなので、やんわりと認識しておきます!

エラーメッセージを表示する

flash[:notice] と flash[:alert]
上記はDeviseで定義されている。
Deviseは、ユーザー認証やセッション管理などの機能を提供するGemであり、様々なメッセージを生成し、それらを flash に格納します。

ユーザーがログインに失敗した場合、flash[:alert] には "Invalid email or password." などのエラーメッセージが格納されます。
なので、ログインに失敗した場合、メッセージが表示されるように記載。

app/views/devise/sessions/new.html.erb

# 省略
<%= flash[:alert] %>
# 省略

新規登録ページに関しては、sharedディレクトリに_error_messages.html.erbファイルを作成し、その中にエラーメッセージをまわせるコードを記載。
別のビュー(パーシャル)を呼び出すために、renderメソッドを使用。 f.object はパーシャルに渡されるローカル変数になる(ここでは@user)。

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

<%= form_with model: @user, url: user_registration_path, class: 'registration-main', local: true do |f| %>
# 省略
<%= render 'shared/error_messages', model: f.object %>
# 省略
<%= end %>

ログアウトの実装

ルーティングを確認。

new_user_session GET    /users/sign_in(.:format)                                                                          devise/sessions#new
destroy_user_session DELETE /users/sign_out(.:format)                                                                         devise/sessions#destroy
new_user_registration GET    /users/sign_up(.:format)                                                                          devise/registrations#new

もしログインしていたら?とい条件分岐でボタンの表示を変える。
※user_signed_in?はDeviseが提供するヘルパーメソッドで、ユーザーがサインインしているかどうか判定する。

ログアウトする際のパスを記載。
link_toのHTTPメソッドはデフォルトでGET なので、
DELETEを指定するために、 data: {turbo_method: :delete}を記載。

<% if user_signed_in? %>
        <li><%= link_to current_user.nickname, "#", class: "user-nickname" %></li>
        <li><%= link_to 'ログアウト', destroy_user_session_path, data: {turbo_method: :delete}, class: "logout" %></li>
      <% else %>
        <li><%= link_to 'ログイン', new_user_session_path, class: "login" %></li>
        <li><%= link_to '新規登録', new_user_registration_path, class: "sign-up" %></li>
      <% end %>

とりあえずここまででサインアップ、ログイン、ログアウトまで実装。

テストコードは後編で振り返ろうと思います。

おわりに

実装したことをつらつら書いてしまったので、あまりまとまりのない記事になってしまいましたが。。。
いろいろ深掘りできたので、今後ユーザー管理が必要なものを作るときに活かせたらと思います!