8.2 ログイン

実際にログイン中の状態での有効な値の送信をフォームで正しく扱えるようする
cookies（ブラウザを閉じると自動的に有効期限が切れるもの）を使った一時セッションでユーザーをログインできるようにする


セッションを実装するには、様々なコントローラやビューでおびただしい数のメソッドを定義する必要がある、しかしセッション生成時にヘルパーモジュールも自動生成されている
Railsの全コントローラの親クラスであるApplicationコントローラにこのモジュールを読み込ませれば、どのコントローラでも使えるようになる

ApplicationコントローラにSessionヘルパーモジュールを読み込む

```app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper
end
```

##8.2.1 log_inメソッド
事前定義済みのsessionメソッドを使って、単純なログインを行えるようにします
sessionメソッドはハッシュのように扱えるので、次のように代入します。

```
session[:user_id] = user.id
```
ブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される
しかしsessionメソッドで作成された一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了してしまう

log_inという名前のメソッドを定義

log_inメソッド

```app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end
```


log_inを使ってユーザーにログインする

```app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
  end
end
```
ユーザーのプロフィールページにリダイレクトする準備ができた


expireの参考
>https://qiita.com/maitake9116/items/f484fffdbd4e6047bd76

##8.2.2　現在のユーザー
current_userメソッドを定義して、セッションIDに対応するユーザー名をデータベースから取り出せるようにします
current_userメソッドの目的は、次のようなコードを書けるようにすること

```
<%= current_user.name %>
```

redirect_to current_userでリダイレクトできるようにもする

```
User.find(session[:user_id])
```
findをつかうと例外を発生してしまう

```
User.find_by(id: session[:user_id])
```

今度はIDが無効な場合（=ユーザーが存在しない場合）にもメソッドは例外を発生せず、nilを返します。

セッションに含まれる現在のユーザーを検索する

```app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す（いる場合）
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
end
```

if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
else
  @current_user
end

や

@current_user = @current_user || User.find_by(id: session[:user_id])
と同義


8.2.3 レイアウトリンクを変更する
ユーザーがログインしているときとそうでないときでレイアウトを変更
ログアウト」リンク、「ユーザー設定」リンク、「ユーザー一覧」リンク、「プロフィール表示」リンクを追加


レイアウトのリンクを変更する方法として考えられるのは、ERBコードの中でif-else文を使用し、条件に応じて表示するリンクを使い分ける

```
<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
<% end %>
```
論理値を返すlogged_in?メソッドが必要
ユーザーがログイン中の状態とは「sessionにユーザーidが存在している」こと、つまりcurrent_userがnilではないという状態を指す
これを確認するには！、否定演算子が必要。

logged_in?ヘルパーメソッド

```app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す（いる場合）
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end
end
```


ログイン中のユーザー用のレイアウトのリンクを変更する

```app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
              <li><%= link_to "Settings", '#' %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>
```

レイアウトに新しいリンクを追加したのでBootstrapのドロップダウンメニュー機能を適用できる状態になりました
これはBootstrapに含まれるCSSのdropdownクラスやdropdown-menuなどを使っています
ドロップダウン機能を有効にするため、Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリの他にjQueryも読み込む必要があります

```
yarn add jquery@3.4.1 bootstrap@3.4.1
```
jQueryとBootstrapのJavaScriptライブラリを、アプリケーションにインストール


 ・WebpackにjQueryの設定を追加する

```config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)

module.exports = environment
```
これでJQueryを有効化


必要なJavaScriptファイルをrequireまたはimportする

```app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("jquery")
import "bootstrap
```
application.jsファイルでjQueryをrequireし、Bootstrapをimport


##8.2.4 レイアウトの変更をテストする
・ログイン用のパスを開く
・セッション用パスに有効な情報をpostする
・ログイン用リンクが表示されなくなったことを確認する
・ログアウト用リンクが表示されていることを確認する
・プロフィール用リンクが表示されていることを確認する

順序になるようにテストを作成する

しかしテスト時に登録済みユーザーとしてログインしておく必要があります。当然ながら、データベースにそのためのユーザーが登録されていなければならない
→fixture（フィクスチャ）で作成

digestメソッドをUserクラス自身に配置して、クラスメソッドにする

fixture向けのdigestメソッドを追加する

```app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }

  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
end
```
テストなので計算コストは最小になっている
この?~:は9.2で解説

ユーザーログインのテストで使うfixture

```test/fixtures/users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
```
fixtureではERbを利用できる
これでhas_secure_passwordで必要となるpassword_digest属性はこれで準備できた


有効な情報を使ってユーザーログインをテストする

```test/integration/users_login_test.rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "login with valid information" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert_redirected_to @user
    follow_redirect!
    assert_template 'users/show'
    assert_select "a[href=?]", login_path, count: 0
    assert_select "a[href=?]", logout_path
    assert_select "a[href=?]", user_path(@user)
  end
end
```





>Rubyのぼっち演算子を使うと、obj && obj.methodのようなパターンをobj&.methodのように凝縮した形で書ける



##8.2.5 ユーザー登録時にログイン
Usersコントローラのcreateアクションにlog_in　@userを追加するだけ。

これをテストするためにlogged_in?の代わりにis_logged_in?ヘルパーメソッドを定義して追加する
このメソッドはテストのセッションにユーザーがあればtrueを返し、それ以外の場合はfalseを返します

テスト中のログインステータスを論理値で返すメソッド

```test/test_helper.rb
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
  fixtures :all

  # テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil?
  end
end
```
ユーザー登録の終わったユーザーがログイン状態になっているかどうかを確認できるようになる


ユーザー登録後のログインのテスト

```test/integration/users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  .
  .
  .
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name:  "Example User",
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    follow_redirect!
    assert_template 'users/show'
    assert is_logged_in?
  end
end
```