アプリを作る　基本的ログイン機構

###ログイン機能を作る

####そのためにコントローラを作る

```rb
ubuntu:~/environment/my_app (basic-login) $ rails generate controller Sessions new
Running via Spring preloader in process 15621
      create  app/controllers/sessions_controller.rb
       route  get 'sessions/new'
      invoke  erb
      create    app/views/sessions
      create    app/views/sessions/new.html.erb
      invoke  test_unit
      create    test/controllers/sessions_controller_test.rb
      invoke  helper
      create    app/helpers/sessions_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/sessions.scss
```

####ログイン機能のためのルーティング
config/routes.rb

```rb
Rails.application.routes.draw do
  get 'sessions/new'
  root 'static_pages#home'
  get  '/signup',  to: 'users#new'
  # signupのurlを入力してuser/newのアクションが動き、htmlが表示される。
  get    '/login',   to: 'sessions#new' <-
  # newアクション
  post   '/login',   to: 'sessions#create' <-
  # creteアクション
  delete '/logout',  to: 'sessions#destroy' <-
  # destroyアクション
  resources :users
  # ユーザー情報を表示するURL（/users/1）を追加するためだけのものではありません
  # ユーザーのURLを生成するための多数の名前付きルートと共に、
  #   アクションが利用できるようになるのです
end
```

####セッションにアクションできることをアクセスすることを
test/controllers/sessions_controller_test.rb

```rb
require 'test_helper'

class SessionsControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get login_path
    # 新しいセッションのページ（ログイン）をgetする
    assert_response :success
    # getリクエストが成功したか確認
  end
end
```

####そしてセッションページを作成

```html
<% provide(:title, "Log in") %>
<h1>ログイン</h1>
<!--セッションページ-->

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session, local: true) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>
```

####そこからログイン機能をつける
app/controllers/sessions_controller.rb

```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])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
      # 有効且つパスワードを認識する
    else
      flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない
      # エラーメッセージを作成する
      #.nowで何かすると消えるようにする
      render 'new'
    end
  end

  def destroy
  # セッションの削除（ログアウト）
  end
end
```

####統合テストのファイルを生成
```
ubuntu:~/environment/my_app (basic-login) $ rails generate integration_test users_login
Running via Spring preloader in process 4690
      invoke  test_unit
      create    test/integration/users_login_test.rb
```


####ログインのテストを書く
test/integration/users_login_test.rb

```rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  test "login with invalid information" do
    get login_path
    # /loginとリクエストする
    assert_template 'sessions/new'
    # loginページが表示されていることを確認
    post login_path, params: { session: { email: "", password: "" } }
    # 無効なデータでログインさせる
    assert_template 'sessions/new'
    # そうするとログインページに戻らせる
    assert_not flash.empty?
    # エラーメッセージが表示されるか確認
    get root_path
    # .comと入力し、リクエストする
    assert flash.empty?
    # ホーム画面に行くとエラーメッセージが消えているか確認
  end
end
```

####ApplicationコントローラにSessionヘルパーモジュールを読み込む
app/controllers/application_controller.rb

```rb
class ApplicationController < ActionController::Base
  include SessionsHelper
  # どのコントローラでも使えるようにする
end
```

####ログインさせるメソッド
app/helpers/sessions_helper.rb

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

####ユーザーをログインさせる
app/controllers/sessions_controller.rb

```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
      # user_url(user)を表す
      # プロフィールページへのルーティングにしています。
    else
      flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない
      # エラーメッセージを作成する
      #.nowで何かすると消えるようにする
      render 'new'
    end
  end

  def destroy
  # セッションの削除（ログアウト）
  end
end
```

####現在ログイン中のユーザーとする

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

  # 現在ログイン中のユーザーを返す（いる場合）
  def current_user
    if session[:user_id]
    # sessionsに含まれるなら
      @current_user ||= User.find_by(id: session[:user_id])
      # それを検索し、絵ログイン中のユーザーcurrent_userとする
    end
  end
end
```

####ログイン中であるかないかでレイアウトを変更する

```html
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "自作アプリ", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_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>
```

####パスワードをハッシュ化させる
app/models/user.rb

```rb
class User < ApplicationRecord
.
.
.
  # 渡された文字列のハッシュ値を返す
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    # コストパラメータはテストでは最小にする
    BCrypt::Password.create(string, cost: cost)
    # stringはハッシュ化する文字列
    # cost ハッシュ化する計算コストを表す
  end
end
```

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

```fixture
michael:
# テストデータ
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
```

####ユーザーログインをテストする

```rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  
  def setup
  # テストユーザー
    @user = users(:michael)
  end
  
  test "login with invalid information" do
    get login_path
    # /loginとリクエストする
    assert_template 'sessions/new'
    # loginページが表示されていることを確認
    post login_path, params: { session: { email: "", password: "" } }
    # 無効なデータでログインさせる
    assert_template 'sessions/new'
    # そうするとやり直しのページに戻らせる
    assert_not flash.empty?
    # エラーメッセージが表示されるか確認
    get root_path
    # .comと入力し、リクエストする
    assert flash.empty?
    # ホーム画面に行くとエラーメッセージが消えているか確認
  end
end

```

###ユーザー登録時にログイン

####ユーザー登録中にログインする
app/controllers/users_controller.rb

```rb
.
.
.
def create
    @user = User.new(user_params) # 実装は終わっていないことに注意!
    if @user.save
      # 保存の成功をここで扱う。
      log_in @user
       # インスタンス変数に変更し、ユーザーページに移動させる
      flash[:success] = "ようこそ自作アプリへ成功しました！"
      redirect_to @user
      # 表示させるユーザーページを
    else
      render 'new'
    end
end
.
.
.
```

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

```rb
ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment'
require 'rails/test_help'

class ActiveSupport::TestCase
  # Run tests in parallel with specified workers
  parallelize(workers: :number_of_processors)

  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
  fixtures :all
  
  # テストユーザーがログイン中の場合にtrueを返す
  def is_logged_in?
    !session[:user_id].nil?
    # ログイン中であるかどうかを検索中
  end
  # Add more helper methods to be used by all tests here...
end
```

####テストユーザーを使い、ログインのテスト
test/integration/users_signup_test.rb

```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!
    # POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド
    assert_template 'users/show'
    assert is_logged_in?
    # ログイン中であるか？
  end
end
```

####ログアウトメソッドを作る
app/helpers/sessions_helper.rb

```rb
module SessionsHelper
.
.
.
  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    # sessionからidを削除する
    @current_user = nil
    # ログイン済みのユーザーを空にする
  end
end
```

####そのメソッドを使い、ログアウトの一連の流れを入力する
app/controllers/sessions_controller.rb

```rb
class SessionsController < ApplicationController
.
.
.
  def destroy
  # セッションの削除（ログアウト）
    log_out
    # sessions_helperでメソッドから使う
    # セッションとログインを削除する
    redirect_to root_url
    # 削除した後 ホーム画面に戻る
  end
end
```

```rb
require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
  
  def setup
  # テストユーザー
    @user = users(:michael)
  end
  
  test "login with valid email/invalid password" do
  # ログインをするまでの過程をチェック
    get login_path
    # 名前付きルートでログイン画面に行く
    assert_template 'sessions/new'
    # 要求されたログイン画面が表示されたか？
    post login_path, params: { session: { email:    @user.email,
                                          password: "invalid" } }
    # ログイン画面に無効なテストデータを入力
    assert_not is_logged_in?
    # test_helper ログイン中でない
    assert_template 'sessions/new'
    # 失敗したらまたログインページが表示されるか？
    assert_not flash.empty?
    # flashメッセージは表示されたか？
    get root_path
    # ホーム画面に行くことを要求する？
    assert flash.empty?
    # flashメッセージは表示されなかったか？
  end

  test "login with valid information followed by logout" do
    get login_path
    post login_path, params: { session: { email:    @user.email,
                                          password: 'password' } }
    assert is_logged_in?
    # ログイン中であるか？
    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)
    # 
    delete logout_path
    assert_not is_logged_in?
    assert_redirected_to root_url
    follow_redirect!
    assert_select "a[href=?]", login_path
    assert_select "a[href=?]", logout_path,      count: 0
    assert_select "a[href=?]", user_path(@user), count: 0
  end
end
```