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

# ユーザー管理機能の実装

### 実装概要
- Deviseの導入
- ユーザー管理機能の実装
- モデルの単体テストコードを書く

### 1. Deviseの導入

```ruby:Gemfile
#中略
gem 'devise'
```
ターミナル
```
% bundle install
```

### deviseの設定ファイルを作成
ターミナル
```
% rails g devise:install
```
### deviseコマンドでUserモデルを作成
ターミナル
```
% rails g devise user
```

### マイグレーションファイルに下記追記

```ruby:20241014085357_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname,           null: false
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.string :first_name,         null: false
      t.string :last_name,          null: false
      t.string :first_name_kana,    null: false
      t.string :last_name_kana,     null: false
      t.date   :birth_date,         null: false
#~中略
```
### バリデーションの設定
他モデルは作成していないのでアソシエーションは記述せず
```ruby:app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname, presence: true
  validates :first_name, presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々ー]+\z/ }
  validates :last_name, presence: true, format: { with: /\A[ぁ-んァ-ヶ一-龥々ー]+\z/ }
  validates :first_name_kana, presence: true, format: { with: /\A[ァ-ヶー]+\z/ }
  validates :last_name_kana, presence: true, format: { with: /\A[ァ-ヶー]+\z/ }
  validates :birth_date, presence: true
end
```
### マイグレーションを実行
ターミナル
```
rails db;migrate
```

### ストロングパラメーターを使えるようにする
deviseの処理を行うコントローラーはGem内に記述されているため、編集することができない
deviseにストロングパラメーターを追加するコードは、deviseのコントローラーが編集できないため、application_controller.rbに記述
```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :basic_auth
  before_action :configure_permitted_parameters, if: :devise_controller?

  private

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up,
                                      keys: [:nickname, :first_name, :last_name, :first_name_kana, :last_name_kana, :birth_date])
  end

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV['BASIC_AUTH_USER'] && password == ENV['BASIC_AUTH_PASSWORD']
    end
  end
end
```

### devise用のビューを作成
ターミナル
```
% rails g devise:views
```

※最終課題はサーバーサイドを重点としたカリキュラムのためビューファイルは全て用意されている。
用意されているビューファイルに置き換える。

```ruby:app/views/devise/registrations/new.html.erb
#~中略
<%= form_with model: @user, url: new_user_registration_path, class: 'registration-main', local: true do |f| %>
#~中略
  <%= render 'shared/error_messages', model: f.object %>
＃~中略
    <%= f.text_area :nickname, class:"input-default", id:"nickname", placeholder:"例) furima太郎", maxlength:"40" %>
#~中略
    <%= f.email_field :email, class:"input-default", id:"email", placeholder:"PC・携帯どちらでも可", autofocus: true %>
#~中略
    <%= f.password_field :password, class:"input-default", id:"password", placeholder:"6文字以上の半角英数字" %>
#~中略
    <%= f.password_field :password_confirmation, class:"input-default", id:"password-confirmation", placeholder:"同じパスワードを入力して下さい" %>
#~中略
      <%= f.text_area :last_name, class:"input-name", id:"last-name", placeholder:"例) 山田" %>
      <%= f.text_area :first_name, class:"input-name", id:"first-name", placeholder:"例) 陸太郎" %>
#~中略
      <%= f.text_area :last_name_kana, class:"input-name", id:"last-name-kana", placeholder:"例) ヤマダ" %>
      <%= f.text_area :first_name_kana, class:"input-name", id:"first-name-kana", placeholder:"例) リクタロウ" %>
#~中略
    <div class='input-birth-wrap'>
      <%= raw sprintf(
                  f.date_select(
                    :birth_date,
                    class:'select-birth',
                    id:"birth-date",
                    use_month_numbers: true,
                    prompt:'--',
                    start_year: 1930,
                    end_year: (Time.now.year - 5),
                    date_separator: '%s'),
                  "<p> 年 </p>", "<p> 月 </p>") + "<p> 日 </p>" %>
    </div>
#~中略
```
※コードは修正部分のみを掲載。

ここで必要な情報を入力して新規登録しようとするも保存できないエラーが発生。
### 1.ルーティングの確認
routes.rbファイルを再度確認。
```ruby:config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'items#index'
end
```
問題なし。

### 2. マイグレーションの確認
ターミナル
```
% rails db:migrate:status

database: furima_41457_development

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20241014085357  Devise create users
```
問題なし。

### 3. フォームの確認
```ruby:app/views/devise/registrations/new.html.erb
#~中略
<%= form_with model: @user, url: new_user_registration_path, class: 'registration-main', local: true do |f| %>
#~中略
```
上記コードにて正しいパスの指定ができていなかったことが判明。
# RailsでのDeviseのルーティング理解
RailsアプリケーションでDeviseを導入した際、ルーティングは非常に重要です。rails routesを実行すると、各ルートのPrefix、HTTPメソッド、URIパターン、対応するController#Actionが表示されます。これを理解するためのコツを以下に示します。

1. Prefixの確認
各ルートのPrefixは、対応するURLへのショートカットです。例えば、user_registrationというPrefixは、user_registration_pathを通じて新規登録のURLにアクセスできます。

2. HTTPメソッドの確認
各ルートの左側に表示されるHTTPメソッド（GET、POSTなど）を確認することで、そのエンドポイントが受け付けるリクエストを把握できます。

3. URIパターンとController#Actionの確認
URIパターンはリクエストするURLの形式を示し、Controller#Actionはそのルートがどのアクションに対応するかを示します。例えば、new_user_registration GET /users/sign_upは新規登録フォームを表示し、user_registration POST /usersはユーザーを作成するアクションです。

具体例
新規登録の流れ:
GET /users/sign_upで新規登録フォームを取得。
POST /usersでフォームのデータを送信し、ユーザーを作成。

コードを以下に修正
```ruby:app/views/devise/registrations/new.html.erb
<%= form_with model: @user, url: user_registration_path, class: 'registration-main', local: true do |f| %>
```

ユーザーデータの保存が確認できたため、トップページのヘッダー表示部分がログイン後とログイン前で変わるように修正。
```ruby:app/views/shared/_header.html
#~中略
<%# deviseを導入できたら、ログインの有無で表示が変わるように分岐しましょう%>
      <% 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>
      <%# //deviseを導入できたら、ログインの有無で表示が変わるように分岐しましょう%>
      <% end %>
#~中略
```
ユーザー詳細ページはまだ未実装のためそのまま。
ログアウト、ログインページを表示するルートをrails routesにて確認し修正。

新規登録機能とログイン・ログアウト機能の実装が完了したので、次に新規登録・ログイン共にエラーハンドリングができるように正規表現を利用してバリデーションの修正を行なっていく。

### バリデーションの設定

```ruby:app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i
  FULL_WIDTH_REGEX = /\A[ぁ-んァ-ヶ一-龥々ー]+\z/
  KATAKANA_REGEX = /\A[ァ-ヶー]+\z/

  validates :nickname, presence: true
  validates :password, format: { with: PASSWORD_REGEX, message: 'is invalid. Include both letters and numbers' }
  validates :first_name, presence: true, format: { with: FULL_WIDTH_REGEX, message: 'is invalid. Input full-width characters' }
  validates :last_name, presence: true, format: { with: FULL_WIDTH_REGEX, message: 'is invalid. Input full-width characters' }
  validates :first_name_kana, presence: true,
                              format: { with: KATAKANA_REGEX, message: 'is invalid. Input full-width katakana characters' }
  validates :last_name_kana, presence: true,
                             format: { with: KATAKANA_REGEX, message: 'is invalid. Input full-width katakana characters' }
  validates :birth_date, presence: true
end
```
```ruby
PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i
```
この正規表現は、パスワードの形式を指定するためのもの。

\A は文字列の先頭を示。
(?=.*?[a-z]) は、少なくとも1つの英小文字が含まれることを要求。
(?=.*?\d) は、少なくとも1つの数字が含まれることを要求。
[a-z\d]+ は、英小文字と数字のみで構成されていることを示している。
\z は文字列の終わりを示す。
i フラグは、大文字と小文字を区別しない（case-insensitive）ことを意味。

```
FULL_WIDTH_REGEX = /\A[ぁ-んァ-ヶ一-龥々ー]+\z/
```
この正規表現は、全角文字（ひらがな、カタカナ、漢字）を含む名前のバリデーションに使う。

\A は文字列の先頭を示す。
[ぁ-んァ-ヶ一-龥々ー]+ は、次の文字を1回以上含むことを示している。
ぁ-ん：ひらがな
ァ-ヶ：カタカナ
一-龥：漢字（CJK統合漢字）
々：繰り返し記号（例：「人々」など）
ー：長音符（カタカナで使用する伸ばし棒）
\z は文字列の終わりを示す。

```
KATAKANA_REGEX = /\A[ァ-ヶー]+\z/
```
この正規表現は、カタカナのみで構成された文字列をバリデーションするために使う。

\A は文字列の先頭を示す。
[ァ-ヶー]+ は、次の文字を1回以上含むことを示している。
ァ-ヶ：全角カタカナ
ー：長音符（カタカナで使用する伸ばし棒）
\z は文字列の終わりを示す。

ログイン時・未ログイン時のヘッダーの表示切り替え、新規登録・ログインページへの遷移、ログアウト機能を一通り行って問題なく動いたので、次回はモデルの単体テストコードを記述してテストを行なっていきたい。