[初学者]　sorceryでユーザー登録、ログイン・ログアウト機能の作成

### 目的
学んだことを忘れないように備忘録として残す
### sorceryとは？
SorceryはRuby on Railsで使用できる認証および認可のGem。これを使用することで、ユーザー認証、パスワードリセット、ユーザー管理などの一般的な認証および認可機能をアプリケーションに簡単に統合できる。
### gemとは？
gem は、Rubyプログラミング言語の拡張機能やライブラリをパッケージ化したもの。
RubyGemsを使用することで、他の開発者が書いたRubyライブラリやアプリケーションを簡単にインストールできる。
# sorceryの追加
gemファイルにsorceryを追加
```ruby
gem ‘sorcery’
```
### gemの反映
bundle installで追加したgemファイルを反映
```ruby
bundle install
```
# sorceryのセットアップ
### Sorcery gem用の設定ファイルと関連するファイルを生成
必要なファイルをプロジェクトに追加し、設定をカスタマイズできるようにする。
```ruby
rails g sorcery:install
```
**生成されるファイル**
- config/initializers/sorcery.rb：Sorceryの設定ファイル
- app/controllers/user_sessions_controller.rb：ユーザーセッション（ログインとログアウト）を管理するためのコントローラー
- app/models/user.rb：ユーザーモデルのテンプレート。ユーザーに関連するメソッドや属性を追加する場合にこのファイルを編集する。
- app/views/user_sessions：ログインフォームやログアウトリンクなどのビューファイルが含まれる。
- db/migrate/*_create_users.rb（実際のファイル名はタイムスタンプによって異なる）：ユーザーモデルに関連するデータベーステーブルを作成するマイグレーションファイル
### マイグレーションファイルの更新
Sorceryを使ったユーザー認証システムが正しく動作し、データベースと連携できるようにする。
```ruby
rails db:migrate
```
# マイグレーションファイルの編集
ユーザ登録の際に姓名を入力できるようにしたいため、データベースにlast_name、first_nameカラムをstring型で追加
### マイグレーションファイルの作成
```ruby
rails g migration AddNameToUsers
```
### 作成したマイグレーションファイルを編集
```ruby
class AddNameToUsers < ActiveRecord::Migration
  def change
    add_column :users, :last_name, :string
    add_column :users, :first_name, :string
  end
end
```
### マイグレーションファイルの更新
作成したマイグレーションファイル(AddNameToUsers)に変更を加えたので、更新する。
```ruby
rails db:migrate
```
# Userモデルのバリデーション設定
app/models/user.rb
Userモデルにバリデーションを追加
```ruby
class User < ApplicationRecord 
  authenticates_with_sorcery!
　#ユーザーが設定したパスワードの長さが3文字以上であることを確認
  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
 #ユーザーが設定したパスワードと確認用のパスワードが一致していることを確認
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
　#確認用パスワードの入力が存在することを確認
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } 

　#新規にユーザーが登録された際に、emailが存在するか、登録済みかどうかを確認
  validates :email, uniqueness: true, presence: true
　#新規にユーザーが登録された際に、first_nameが存在するかどうかの確認と文字数の制限
  validates :first_name, presence: true, length: { maximum: 255 }
　#新規にユーザーが登録された際に、last_nameが存在するかどうかの確認と文字数の制限
  validates :last_name, presence: true, length: { maximum: 255 } 
end
#confirmationは、2つの属性の値が一致するかどうかを検証
#presenceは、指定された属性が存在しているかどうかを検証
```
new_record?: レコードが新しいレコードかどうかを確認するメソッド
changes[:crypted_password]: レコードの crypted_password フィールドに変更があるかどうかを確認
# ユーザー登録機能の作成
usersコントローラとビュー生成
```ruby
rails g controller users new
```
app/controllers/users_controller.rbとapp/views/users/new.html.erbが生成される。
### user_controllerへの記述
```ruby
class UsersController < ApplicationController

  def new; end

  def create
　　# @userにuser_paramsに入ったデータを代入
    @user = User.new(user_params)
    if @user.save
　　　　#login_path ヘルパーメソッドで、保存が成功したらログインページへ飛ばす
      redirect_to login_path
    else
      #保存が失敗したら登録画面へ飛ばす
      render 'new'
    end
  end

  private
　#ユーザー登録画面からUserモデルへ送信されたパラメーターを要求し、その中からpermitで指定したパラメータを取得
  def user_params
     params.require(:user).permit(:last_name, :first_name, :email, :password, 
:password_confirmation)
  end
end
```
### ユーザーの新規登録ページの作成
new.html.erbへの記述
form_withで投稿フォームを作成するときは、`form.htmlタグ名 :カラム名`とする。 (カラム名は保存される先のテーブルのカラム名を指定)
上記の場合は、登録ボタンが押されたら、Userモデルの各カラムに入力内容が保存されるようする。
```ruby
<div class="container">
  <div class="row">
    <div class="col-md-10 col-lg-8 mx-auto">
      <h1>ユーザー登録</h1>
      <%= form_with model: @user do |f| %>
        <div class="mb-3">
          # ユーザーがこのフォーム内の last_name テキストフィールドに入力した値が、
          # フォームの送信時に last_name という名前のパラメーターとしてサーバーに送信される
          <%= f.label :last_name, class: "form-label" %>
          <%= f.text_field :last_name, class: "form-control" %>
        </div>
        <div class="mb-3">
          <%= f.label :first_name, class: "form-label" %>
          <%= f.text_field :first_name, class: "form-control" %>
        </div>
        <div class="mb-3">
          <%= f.label :email, class: "form-label" %>
          <%= f.email_field :email, class: "form-control" %>
        </div>
        <div class="mb-3">
          <%= f.label :password, class: "form-label" %>
          <%= f.password_field :password, class: "form-control" %>
        </div>
        <div class="mb-3">
          <%= f.label :password_confirmation, class: "form-label" %>
          <%= f.password_field :password_confirmation, class: "form-control" %>
        </div>
        <%= f.submit "登録", class: "btn btn-primary" %>
      <% end %>
      <div class='text-center'>
        <%= link_to 'ログインページへ', login_path %>
      </div>
    </div>
  </div>
</div>
```
### ルートの設定
config/routes.rb
```ruby
Rails.application.routes.draw do
　#ルートURLにアクセスされた場合、static_pages コントローラーの top アクションを呼び出す
  root 'static_pages#top' 

　#/login にアクセスされた場合、user_sessions コントローラーの new アクションを呼び出す
  get 'login', to: 'user_sessions#new'
　#/login に対してHTTP POSTリクエストが送信された場合、user_sessions コントローラーの create アクションが呼び出す
  post 'login', to: 'user_sessions#create'
　#/logout に対してHTTP DELETEリクエストが送信された場合、user_sessions コントローラーの destroy アクションが呼び出す
  delete 'logout', to: 'user_sessions#destroy' 

　#新規ユーザーの作成画面を表示する new アクションと、ユーザーの作成を行うcreateアクションを提供するためのURLが自動的に生成される
  resources :users, only: %i[new create]
end
```
# ログイン機能の作成
user_sessionsコントローラを生成します
```ruby
rails g controller user_sessions
```
app/controllers/user_sessions_controller.rbが生成される。
UserSessionController にログインの処理を記述
app/controllers/user_sessions_controller.rb
```ruby
class UserSessionsController < ApplicationController
　　# require_loginフィルターを new と create アクションに適用しないように指示
   # 新しいユーザーを作成するための new アクションと create アクションでは、ログインが必要ないことを示す
   skip_before_action :require_login, only: %i[new create]

   def new; end

   def create
     @user = login(params[:email], params[:password])
     if @user
       # ログインに成功した場合、root_pathにリダイレクト
       redirect_to root_path
     else
       # ログインに失敗した場合、ログインページにリダイレクト
       render :new
     end
   end
end

```
skip_before_action :require_login, only: %i[new create]を設定するのは、ログインを行う前にログインページにアクセスできて、ログインしていない状態でcreateアクションを実行する必要があるため。
### ヘッダーの作成
ログイン前のヘッダーを作成して、ログイン・未ログイン状態によって表示を変えれるようにする。
まずは、ログイン・未ログイン状態でヘッダーが切り替わるように設定。
app/views/layouts/application.html.erb
```ruby
<body>
  <% if logged_in? %>
    <%= render 'shared/header' %>
  <% else %>
    <%= render 'shared/before_login_header' %>
  <% end %>
  <%= yield %>
  <%= render 'shared/footer' %>
</body>
```
未ログイン時のヘッダーを作成
app/views/shared/_before_login_header.html.erb
```ruby
<header>
  # ナビゲーションバーを定義するためのHTML要素を生成
  <nav class="navbar navbar-expand-lg navigation navbar-light bg-light">

    # ロゴ画像を表示するためのコード
    <%= link_to root_path, class: "navbar-brand" do %>
      <%= image_tag "#" %> # 任意の画像を入れる
    <% end %>  

    # Bootstrapのナビゲーションバーでハンバーガーメニューを定義するためのHTML要素を生成
    <button class='navbar-toggler' data-bs-toggle='collapse' data-bs-target='#navbarSupportedContent' aria-controls='navbarSupportedContent' aria-expanded='false' aria-label='Toggle navigation'>
      <span class="navbar-toggler-icon"></span>
    </button>
    
    # ナビゲーションバーで折りたたみメニューを定義するためのHTML要素を生成
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      # ナビゲーションバーにメニュー項目を追加するためのHTML要素を生成
      <ul class="navbar-nav ms-auto main-nav align-items-center">
        #  ログインというテキストにリンクを作成
        <li class="nav-item">
          <%= link_to 'ログイン', login_path, class: "nav-link" %>
        </li>
      </ul>
    </div>
  </nav>
</header>
```
ログイン時のヘッダーを作成
app/views/shared/_login_header.html.erb
```ruby
<header>
　　<nav class="navbar navbar-expand-lg navigation navbar-light bg-light">
      <%= link_to root_path, class: "navbar-brand" do %>
        <%= image_tag "#" %> # 任意の画像を入れる
      <% end %>
      # Bootstrapを使用してナビゲーションバーのトグル（折り畳み）ボタンを作成
      <button class='navbar-toggler' data-bs-toggle='collapse' data-bs-target='#navbarSupportedContent' aria-controls='navbarSupportedContent' aria-expanded='false' aria-label='Toggle navigation'>
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class='collapse navbar-collapse' id='navbarSupportedContent'>
        <ul class='navbar-nav ms-auto main-nav align-items-center'>
　　　　　　　<li class="nav-item">
             # ユーザーがログアウトを実行した際には、UserSessionsController の destroy アクションが呼び出され、logout メソッドが実行される
              <%= link_to 'ログアウト', logout_path, class: 'dropdown-item' %>
           </li>
        </ul>
      </div>
　　</nav>
</header>
```
# ログアウト機能の作成
UserSessionController にログアウトの処理を記述
app/controllers/user_sessions_controller.rb
```ruby
class UserSessionsController < ApplicationController
　　# require_loginフィルターを new と create アクションに適用しないように指示
　　# 新しいユーザーを作成するための new アクションと create アクションでは、ログインが必要ないことを示す
　　skip_before_action :require_login, only: %i[new create]

　　def new; end

　　def create
     @user = login(params[:email], params[:password])
     if @user
       # ログインに成功した場合、root_path(トップページ)にリダイレクト
       redirect_to root_path
     else
       # ログインに失敗した場合、ログインページにリダイレクト
       render :new
     end
   end
 
　　def destroy
      # logoutメソッドを呼び出して、セッションをクリアする
      logout
      # ログアウトに成功した場合、root_path(トップページ)にリダイレクト
　　　　redirect_to root_path
　　end
end
```