0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

8.2 ログイン

Posted at

実際にログイン中の状態での有効な値の送信をフォームで正しく扱えるようする
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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?