実際にログイン中の状態での有効な値の送信をフォームで正しく扱えるようする
cookies(ブラウザを閉じると自動的に有効期限が切れるもの)を使った一時セッションでユーザーをログインできるようにする
セッションを実装するには、様々なコントローラやビューでおびただしい数のメソッドを定義する必要がある、しかしセッション生成時にヘルパーモジュールも自動生成されている
Railsの全コントローラの親クラスであるApplicationコントローラにこのモジュールを読み込ませれば、どのコントローラでも使えるようになる
ApplicationコントローラにSessionヘルパーモジュールを読み込む
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メソッド
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
end
log_inを使ってユーザーにログインする
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の参考
##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を返します。
セッションに含まれる現在のユーザーを検索する
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?ヘルパーメソッド
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
ログイン中のユーザー用のレイアウトのリンクを変更する
<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の設定を追加する
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する
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メソッドを追加する
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
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
fixtureではERbを利用できる
これでhas_secure_passwordで必要となるpassword_digest属性はこれで準備できた
有効な情報を使ってユーザーログインをテストする
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を返します
テスト中のログインステータスを論理値で返すメソッド
ENV['RAILS_ENV'] ||= 'test'
.
.
.
class ActiveSupport::TestCase
fixtures :all
# テストユーザーがログイン中の場合にtrueを返す
def is_logged_in?
!session[:user_id].nil?
end
end
ユーザー登録の終わったユーザーがログイン状態になっているかどうかを確認できるようになる
ユーザー登録後のログインのテスト
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