Deviseの導入

Ruby on Railsのバージョン:7.2.1
Rubyのバージョン:3.3.5

Bootstrapの導入と適用をした後の続きです。
Deviseを使用して、認証機能を追加していきます。

Deviseとは？
1.ユーザーの新規登録（サインアップ機能）
2.ログイン、ログアウト
3.パスワードのリセット
4.メールアドレスの確認（ユーザーの新規登録時にメールを送って認証用のリンクを踏ませてアカウントの有効化を行う）など
Deviseをインストール、セットアップ（rails generate devise:install）、モデル生成、マイグレーションまで行うと上記機能が簡単に実装できます。非常に便利です。

## Deviseの導入
①Gemのインストール
下記をGemfileに記載し、bundle installする
```.Gemfile
gem "devise"
```
```.terminal
bundle install
```
②Deviseのセットアップ
下記コマンドを実行
```.terminal
rails generate devise:install
```
下記ファイルが生成されます。
初期設定ファイル：config/initializers/devise.rb　→　Deviseのgemファイル
ロケールファイル：config/locales/devise.en.yml　→ Deviseの認証Gemで使用される国際化（i18n）対応の翻訳ファイル。エラーメッセージ、メールの件名、パスワードリセットの手順のメッセージなどの設定、カスタマイズができる。

## Deviseの設定と確認
①Userモデルの作成
```.terminal
rails generate devise User
```
下記が生成されます。
Userモデル（デフォルトでDeviseがサポートする機能が含まれます）
マイグレーションファイル（ユーザー認証に必要なカラムが含まれる）

②マイグレーションを実行
```.terminal
rails db:migrate
```

③ルーティングの設定の確認
Userモデルを生成すると自動的にconfig/routes.rbに下記が記載されているので、記載があるか確認する。
```routes.rb
devise_for :users
```
```.上記ルーティング設定で作成されるルート
rails routes -g user
                  Prefix Verb   URI Pattern                    Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
           user_password PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
                         POST   /users/password(.:format)      devise/passwords#create
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
       user_registration PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
                         POST   /users(.:format)               devise/registrations#create
```

ここまでできれば基本的なDeviseの機能は使える状態になっていると思います。
試しに、サーバーを立ち上げてhttp://localhost:3000/users/sign_in に飛んでみましょう！
下記のようなページが表示されればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/f9990fb1-5c70-a2dd-9e68-bdc66ffb7303.png)

## ※viewを編集したくなったら
```.terminal
rails generate devise:views
```
をコマンド実行するとDeviseのビューファイルが作成されます。
自分の好きなデザインなどに変えたい方はこちらを実行してください。

## ルートパスとなるページを作成する
非ログインしていないと新規投稿や編集ページなどにアクセスできないようし、アクセスするとルートパスにリダイレクトするように設定します。そのためルートパスとして簡単なページを作成していきます。

①rails g controller homes topを実行
コントローラーとアクションを作成します
```.terminal
rails g controller homes top
```
②ルーティングを設定。
routes.rbに下記を記載することによってルートパスをhomes#topに設定する
```routes.rb
root to: "homes#top"
```
③top.html.erbビューファイルを作成する
bootstrapのクラスを利用してルートパスとなるページを作成します。
```top.html.erb
<h1>Homes#top</h1>
<%= link_to "サインイン", new_user_session_path, class: "btn btn-primary" %>
<%= link_to "サインアップ", new_user_registration_path, class: "btn btn-primary" %>
<%= link_to "投稿一覧", posts_path, class: "btn btn-primary" %>
```
これでルートパスとなるページの作成と設定が完了しました。
画面は下記のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3931580/1ea0aa44-05d5-630d-63b3-96f0500c5964.png)

## ログイン時と非ログイン時の動作設定
非ログイン時にindex,new,create,edit,update,destroyのアクションを操作できないように設定します。

①コントローラーにbefore_action :authenticate_user!を追加する
```posts.controller.rb
before_action :authenticate_user!, only: [:index, :new, :create, :edit, :update, :destroy]
```
**authenticate_user!** はDeviseが提供するメソッド。コントローラーに追加すると、ログインしていないユーザーはそのアクションにアクセスできなくなります。←重要

②リダイレクト先の設定
ApplicationControllerにログイン、ログアウト後のリダイレクト先を指定する。
```.applicationController
def after_sign_in_path_for(resource)
    posts_path # ログイン後に投稿一覧ページにリダイレクト
end

def after_sign_out_path_for(resource_or_scope)
    root_path # ログアウト後にホームページにリダイレクト
end
```
**after_sign_in_path_for
after_sign_out_path_for**
はDeviseの全体的な挙動を制御する**コールバックメソッド**と呼ばれるものです。
カスタマイズすることによってリダイレクト先を任意のURLやパスへ変更できます。

### 通常のアクション（createなど）
ユーザーからの直接的なリクエスト（例: フォーム送信）によって動作する。
明確なリクエスト（HTTPメソッドとエンドポイント）に基づく。
### コールバックメソッド（after_sign_in_path_forなど）
Deviseがログイン/ログアウトの処理を行った後、内部的に自動的に呼び出される。
ユーザーが直接的にこれらのメソッドをリクエストするわけではない。

これでログイン時に投稿一覧ページへ、ログアウト時にホームパス（Homes#top）にリダイレクトします。

③ログインしているユーザーのみが投稿できるようにする
UserモデルとPostモデルのアソシエーション設定する
```.各モデル
class User < ApplicationRecord
  has_many :posts, dependent: :destroy
end

class Post < ApplicationRecord
  belongs_to :user
end
```
これにより、current_user.postsが利用可能になります。

次にpostsテーブルにuser_idカラムを追加します
下記を実行します。
```.terminal
rails g migration AddUserIdToPosts user:references
rails db:migrate
```
**user:references** を使用してuser_idカラムを作成し、PostがUserに紐づくように設定します。

次にposts_controller.rbのcreateアクションを修正
```posts_controller.rb
def create
    @post = current_user.posts.build(user_params)
    if @post.save
      redirect_to posts_path, notice: "投稿を作成しました"
    else
      render :new, status: :unprocessable_entity 
    end
end
```
**current_user.posts.build(user_params)** でログイン中のユーザーが作成した投稿を紐付けます。

④非ログイン時に編集、投稿削除ができないように設定する
posts.controller.rbに下記を記載
```posts.controller.rb
before_action :correct_user, only: [:edit, :update, :destroy]
~
~
private

  def correct_user
      unless @post.user == current_user 
        redirect_to posts_path, alert: "他のユーザーの投稿を編集または削除することはできません。"
      end
  end
```
correct_userはアクセス制御するためのメソッドです。
**current_user** はDeviseが提供するメソッドで、ログイン中のユーザーを取得します

before_action :correct_user, only: [:edit, :update, :destroy]と記載することによって、非ログインユーザはposts_pathにリダイレクトするようなりました。

以上で大丈夫かと思ったのですが、ログアウトした後に戻るボタンを押して削除ボタンを押したりするとActionController::InvalidAuthenticityToken in PostsController#destroyとエラーが出ます。
これはログアウトしたことによってセッションが無効化されているため、CSRFトークンが一致せず、エラーが起きている状態になります。このような状態は避けたいため、ブラウザのキャッシュを無効化することによって対応していきます。

⑤キャッシュの無効化
下記を記載
```posts.controller.rb
before_action :set_cache_buster
~
~
private

  def set_cache_buster
      response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
      response.headers["Pragma"] = "no-cache"
      response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
  end
```
上記のように記載することでキャッシュを使用しないように設定できます。

一旦終了します。