Gem sorceryを使い倒す① 基本のログイン機能

## 環境
ruby "3.2.2"
rails "7.0.7"
Docker


## Gemのインストール
https://github.com/Sorcery/sorcery

　
**【Gemのインストール】**

Gemfileに以下を記述し、ターミナルでbundle installします。
<details><summary>Dockerを使用している場合の注意点</summary>

:::note warn
Dockerを使用している場合はコンテナ内に入ってから記述します。コンテナの外にいる状態で記述した場合、コンテナに入れなくなる場合があります。その場合は一旦削除してコンテナに入り再び記述し直してください。
:::
</details>

```ruby:Gemfile
gem 'sorcery'
```

```ruby
bundle install
```
＊インストールもDocker内で行います。


　

ターミナルで次のコマンドを実行して、コア移行ファイル、初期化ファイル、および `User`モデル クラスを生成します。

```ruby
rails generate sorcery:install
```

　
以下のファイルが作成されます。

`app/models/user.rb`
`config/initializers/sorcery.rb`
`db/migrate/202◯◯◯◯◯◯◯◯◯◯_sorcery_core.rb`
`spec/factories/users.rb`
`spec/models/user_spec.rb`
　

**【マイグレーションファイルの確認】**
```ruby:db/migrate/202◯◯◯◯◯◯◯◯◯◯_sorcery_core.rb
class SorceryCore < ActiveRecord::Migration[6.1]
  def change
    create_table :User do |t|
      t.string :email,            null: false, index: { unique: true }
      t.string :crypted_password
      t.string :salt

      t.timestamps                null: false
    end

    add_index :User, :email, unique: true
  end
end
```
sorceryの基本のUserテーブルが作成されています。

カラムはemailとpasswordです。`crypted_`と`salt`はパスワードをハッシュ化するためのものです。
emailは`null: false`で入力必須、`index: { unique: true }`で同じアドレスが登録できないバリデーションがかけられています。

もしUserテーブルにnameカラムを作成したい場合はここで記述します。
```ruby:例
      t.string :email,          null: false, index: { unique: true }
      t.string :crypted_password
      t.string :salt
      t.string "first_name",    null: false
      t.string "last_name",     null: false
```
確認後、ターミナルでdb:migrateします。
```
rails db:migrate
```



  　

## sorcery基本のログイン編
　
**【Userモデルを作成】**
```ruby:models/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery!
  
end
```
authenticates_with_sorcery! は、Userモデルにsorceryによる認証機能を持たせています。
この下にバリデーションを記述していきましょう。
```ruby:例
  authenticates_with_sorcery!

  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] }
  validates :email, presence: true, uniqueness: true
  validates :reset_password_token, presence: true, uniqueness: true, allow_nil: true
```


　
**【コントローラーを作成】**
```
rails g controller user_sessions
```
以下のファイルが作成されます。

`app/controllers/user_sessions_controller.rb`
`app/views/user_sessions`
`test/controllers/user_sessions_controller_test.rb`
`app/helpers/user_sessions_helper.rb`

　
controllerの中身を作成しましょう。こちらが超基本のかたちです。
[公式参考ページ](https://github.com/Sorcery/sorcery/wiki/Simple-Password-Authentication)

```ruby:controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
 def new; end
 
 def create
   @user = login(params[:email], params[:password])
   if @user
     redirect_back_or_to root_path
   else
     render :new
   end
 end
 
 def destroy
   logout
   redirect_to root_path
 end
end
```

<table>
  <caption>sorceryで提供されるメソッド</caption>
  <thead>
    <tr>
      <th>名前</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> login </td> <td>(params[:email], params[:password])でformに入力されたemailとpasswordの値を受け取ります。
    emailによるUser検索、パスワードの検証を行い、正常に処理できるとセッションデータにUserレコードのid値を格納する、という処理が行われています。</td>
  </tr>
  <tr>
    <td> logout </td> <td>loginセッションをリセットします。</td>
  </tr>
  <tr>
    <td> redirect_back_or_to </td> <td>未ログイン時アクセス不可のページを開こうとした際、require_loginメソッドでユーザをログインページに誘導し、ログインが成功したら、最初に訪れようとしていたページにリダイレクトさせるということが可能になります。</td>
  </tr>
</table>


　

**【ルーティングの編集】**
```
get 'login', to: 'user_sessions#new'
post 'login', to: 'user_sessions#create'
delete 'logout', to: 'user_sessions#destroy'
```
これでlogin_path、logout_pathが使えるようになります。
生成されたpathを確認してみましょう。
http://localhost:3000/rails/info/routes
　

　
**【ビューの作成】**

ログインフォーム
```
<%= form_with url: login_path do |f| %>
  <%= f.label :email %>
  <%= f.email_field :email %>
  
  <%= f.label :password %>
  <%= f.password_field :password %>
  
  <%= f.submit "ログイン" %>
<% end %>
```
　fieldに入力された値をuser_sessions_controllerのcreateで受け取ります。
　
　
## sorceryの便利なメソッド

　
**【logged_in?】**
ログインしているかの有無で表示を変えたり処理を変えたりすることができます。
例えば、ログインしていなければログインリンクのついたヘッダーを、ログインしていればメニューが表示されるヘッダーを等切り替えることができます。
```ruby:例
<body>
    <% if logged_in? %> # ログインしていたら
      <%= render 'shared/header' %> # こっちを表示
    <% else %> # してなかったら
      <%= render 'shared/before_login_header' %> # こっちを表示
    <% end %>
    
    <%= render partial:"shared/flash_message" %>
    <%= yield %>
    <%= render 'shared/footer' %>
  </body>
```
　
 
**【require_login】**
未ログイン状態で表示させられるページを制限できます。
```ruby:controllers_application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login
end
```
application_controllerでbefore_action :require_loginを記述するとすべてのコントローラーに対して適応されます。つまりこの状態だと、すべての画面がログインしていないとアクセスできなくなります。

```ruby:controller/user_session_controller.rb
class UserSessionsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]
end
 ```
ログインを必要としないページのアクションをskip指定し、require_loginを回避します。
一般的にHome、Top画面に指定したページと、ユーザー登録、ログイン画面等、ログイン前に訪れるページはrequire_loginを回避します。
 
　
　
**【not_authenticated】**
ログインしていない場合に呼ばれるメソッドです。
『ログインしていなかったらフラッシュメッセージを表示する』『ログインしていなかったらこのページにリダイレクトをする』などの処理を記述します。
```ruby:controllers/application_controller.rb

private

def not_authenticated
  redirect_to login_path
  flash[:danger] = 'ログインしてください'
end
```



　
## 参考サイト

https://note.com/tumu1632/n/n9f332de38563

https://boku-boc.hatenablog.com/entry/2020/10/10/213625

https://github.com/Sorcery/sorcery/wiki/Simple-Password-Authentication　

https://linyclar.github.io/rails_memos/libraries/sorcery/


 
## 関連記事

https://qiita.com/joinus_ibuki/items/6cdf5368d4b858abfb51

https://qiita.com/joinus_ibuki/items/d09435418704c666ad30

https://qiita.com/joinus_ibuki/items/ce2fcdb9abb980959a9d