Rails ログイン機能　[sorcery]　解説

#Rails ログイン機能実装手順　（自分用）



・sorceryをGemfileに追加する

```:Gemfile
gem ‘sorcery’
```
***
・次にbundle install

```:ターミナル
$ bundle install
```
***
・sorceryに追加されたジェネレータを実行して、アプリの構築をする

```:ターミナル
$ rails g sorcery:install
```
・マイグレーションファイルとuserモデルが作成される
***
```:ターミナル
$ rails db:migrate
```
・で導入完了

#sorcery:installで生成したUserモデルにバリデーションを追加
・Model
以下のようにバリテーションを追加

```rb:app/models/user.rb
class User < ActiveRecord::Base
  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, uniqueness: true, presence: true
  validates :first_name, presence: true, length: { maximum: 255 }
  validates :last_name, presence: true, length: { maximum: 255 }
end
```

#sorcery:installで生成したマイグレーションファイルに必要なカラムを追加する
・ここではlast_nameとfirst_nammeを追加

```rb:db/migrateXXXXXXXXXXXXXXX_sorcery_core.rb
class SorceryCore < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :email,            null: false
      t.string :crypted_password
      t.string :salt
      t.string :last_name, null: false #null: falseは空欄入力では名前とは認めないという意味
      t.string :first_name, null: false

      t.timestamps                null: false
    end

    add_index :users, :email, unique: true
  end
end
```
・マイグレートする

```:ターミナル
$ rails db:migrate
```

#ユーザーの新規登録画面を作成（scaffoldを使ってuserコントローラーを作成。）

```:ターミナル
$ rails g scaffold_controller user email:string crypted_password:string salt:string first_name:string last_name:string
```

・usersファイルにある、crypted_password、saltの部分はビューに表示しないので削除してpasswordとpassword_confirmationを代わりに入れる。（GitHubのREADMEにはこの手順で書いてあるが、最初から「rails g scaffold_controller user email:string password:string　password_confirmation:string first_name:string last_name:string」で実行してもいいと思う。）
***

・Controller

```rb:app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new #(新規登録で作ったデータを@userに代入する)
  end

  def create
    @user = User.new(user_params)#（postメソッドでUser.newからのデータが送られてきている）新規登録で作ったデータに(user_params)という引数をつけて@userに代入する  
    if @user.save#訳：@userが保存できたら
      redirect_to login_path #（ログイン出来たら「user_sessionsのnew」に飛ぶ(redirect_to)ようになる。*「user_sessionsについては後に説明）
    else#訳：@userが保存できなかったら
      render :new #（ログイン出来なければ、usersのnewに戻る（render）） 
    end
  end

  private

  def user_params
        params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name, :salt)
#Task.newで作られたparams[:user]からemail、password、password_confirmation、first_name,last_nameだけを受け取るようにする。
   end
end
```
***
・app/views/users/new.html.erbの編集

```rb:app/views/users/new.html.erb
<div class="container">
  <div class="row">
    <div class="col-md-10 offset-md-1 col-lg-8 offset-lg-2">
      <h1>ユーザー登録</h1>
      <%= form_with model: @user, local: true do |f| %>
        <div class="form-group">
          <%= f.label :last_name %>
          <%= f.text_field :last_name, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :first_name %>
          <%= f.text_field :first_name, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :email %>
          <%= f.email_field :email, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :password %>
          <%= f.password_field :password, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :password_confirmation %>
          <%= 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>
```
・新規登録の画面では、登録するデータをデータベースに保存する必要があるので「form_with model」を使用している。

***
・ルーティングを定義する

```onfig/routes.rb
  get 'login', to: 'user_sessions#new' #login_pathが使えるようになり、login_pathが使われるとログイン画面（'user_sessions#new'）に移動する
  post 'login', to: 'user_sessions#create' #login_path('user_sessions#new')からのデータを'user_sessions#create'へ受け取って、データベースに保存
  delete 'logout', to: 'user_sessions#destroy' #logout_pathが使えるようになる。削除処理を行う。

  resources :users, only: %i[new create]#usersではnewとcreateしか使わないのでonlyで指定している
```
***
#ログイン画面を作成

```:ターミナル
% rails g controller UserSessions new create destroy
```
・ログインに必要なuser_sessions_controllerを作成
***
・コントローラの親にあたる、application_controllerに共通のメソッドを追加する

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :require_login

  private

  def not_authenticated
    redirect_to login_path, danger: "ログインしてください"
  end
end
```
・before_actionはコントローラー内の全てのアクションが実行される前に、その後に続くメソッド（ここではrequire_login）が実行される。
・require_loginメソッドはloginしているかどうかを検証するメソッド。loginできれば、次に進もうとしていたページにリダイレクトできる。loginできなければ「not_authenticated」メソッドが実行される。
・not_authenticatedメソッドは、require_loginでlogin出来なかった場合に実行されるメソッド。ここでは「redirect_to login_path」となっているのでログイン画面（'user_sessions#new'）にリダイレクトされる。
***
・user_sessions_controllerの編集

```app/controllers/user_sessions_controller.rb
class UserSessionsController < ApplicationController
  skip_before_action :require_login, only: %i[create new] #下記で説明
  def new; end

  def create
    @user = login(params[:email], params[:password])　#emailとpasswordが一致していれば@userにデータが代入される(loginメソッドで検証を行なっている)
    if @user #訳：loginメソッドで検証が一致して、@userにデータだ代入されたら
      redirect_back_or_to root_path #ログインしたらroot_path(root toなどで指定したページ)にリダイレクトする。
    else
      render :new #ログイン出来なかったら、ログインページ（'user_sessions#new'）にリダイレクトされる
    end
  end
end
```
#####skip_before_action :require_login, only: %i[create new]の説明
・skip_before_actionでbefore_actionをスキップするという意味になる。つまりここでは、[create new]はログインする際に訪れないといけないページなので、ここで「before_action :require_login」が実行されるとそもそもログイン出来なくなる。なので、「only: %i[create new]」でこのページはログインしているかの検証はしないということになる。
***
・ログインフォーム作成

```rb:app/views/user_sessions/new.html.erb
<div class="container">
  <div class="row">
    <div class=" col-md-10 offset-md-1 col-lg-8 offset-lg-2">
      <h1>ログイン</h1>
      <%= form_with url: login_path, local: true do |f| %>
        <div class="form-group">
          <%= f.label :email %>
          <%= f.text_field :email, class: 'form-control' %>
        </div>
        <div class="form-group">
          <%= f.label :password %>
          <%= f.password_field :password, class: 'form-control' %>
        </div>
        <div class="actions">
          <%= f.submit 'ログイン', class: 'btn btn-primary' %>
        </div>
      <% end %>
      <div class='text-center'>
        <%= link_to '登録ページへ', new_user_path %>
        <a href="#">パスワードをお忘れの方はこちら</a>
      </div>
    </div>
  </div>
</div>
```
・ログインページでは、新規登録と違いデータベースに保存する必要がなく、パスワードとメールアドレスが一致しているかのデータ検証が目的なので「form_with url」を使用している。

***


#ログインしている時としていない時のトップページを分ける（logged_in?メソッド）
・logged_in?メソッドはsorceryで使えるようになるメソッドで、ログインしているかどうかを判断するメソッド。

```rb:app/views/layouts/application.html.erb
  <body>
    <% if logged_in? %>
      <%= render 'shared/header' %>#ログインしている時のrender先
    <% else %>
      <%= render 'shared/before_login_header' %>#ログインしていない時のrender先
    <% end %>
    <%= yield %>
    <%= render 'shared/footer' %>
  </body>
```

#ログイン機能をヘッダーに追加する
※Bootstrapで全体的なレイアウトやヘッダーのプルダウンが機能している

```rb:app/views/shared/_before_login_header.html.erb
<header>
  <nav class="navbar navbar-expand-lg navigation navbar-light bg-light">

    <button class='navbar-toggler' data-toggle='collapse' data-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 ml-auto main-nav align-items-center">
        <li class="nav-item">
          <%= link_to 'ログイン', 'login_path', class: 'nav-link' %> #リンク先をlogin_pathまたは、’login’にする
        </li>
      </ul>
    </div>
  </nav>
</header>
```
・リンク先を「login_path」にすることで、コントローラーで設定したように、ログイン画面に飛ぶことができる。
#ログアウト機能をヘッダーに追加する
※Bootstrapで全体的なレイアウトやヘッダーのプルダウンが機能している

```rb:app/controllers/user_sessions_controller.rb
def destroy
    logout
    redirect_to root_path #リダイレクト先をログイン画面に指定する
  end
```


```rb:app/views/shared/header.html.erb
 <%= link_to 'ログアウト', logout_path, class: 'dropdown-item', method: :delete %>
```
・リンク先をログアウトに指定し、deleteメソッドを追加