基本用語
メモ化
引数に対するメソッドの戻り値を保存しておき、再び同じ引数でメソッドが呼び出された時にその値を再利用することにより、同じ計算を何度もすることを防ぐ最適化手法のひとつ。
文法
ページ内リンクを作る
# 飛びたいタグにid名をつける
<h2 id="example">これは見出しです。</h2>
# URLのかわりにhref=”#ジャンプ先のid名”というように書く
<a href="#example">ここをクリック!</a>
# 違うページの途中に飛ぶためには、"飛びたいページのURL#id名"
<a href="https://saruwakakun.com#example">ここをクリック!</a>
rails routes
現状のルーティングを確認することができる。
rails routes
grep
Usersリソースに関するルーティングだけを表示させる。
rails routes | grep users#
特定のテストファイルだけを実行する
rails test test/integration/users_login_test.rb
cookiesを調べる
以下を参照。
GoogleChromeで特定のサイトのクッキー(Cookie)情報を確認する方法
セッション
HTTPはステートレス(Stateless) なプロトコル。
文字通り「状態(state)」が「ない(less)」ので、HTTPのリクエスト1つ1つは、それより前のリクエストの情報をまったく利用できない、独立したトランザクションとして扱われる(しかし、だからこそこのプロトコルは非常に頑丈)。
この本質的な特性のため、ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内「には」まったくない。
ユーザーログインの必要なWebアプリケーションでは、セッション(Session)と呼ばれる半永続的な接続をコンピュータ間(ユーザーのパソコンのWebブラウザとRailsサーバーなど)に別途設定する。
セッションはHTTPプロトコルと階層が異なる(上の階層にある)ので、HTTPの特性とは別に (若干影響は受けるものの)接続を確保できる。
Railsでセッションを実装する方法として最も一般的なのは、cookiesを使う方法。
cookiesとは、ユーザーのブラウザに保存される小さなテキストデータ。
cookiesは、あるページから別のページに移動した時にも破棄されないので、ここにユーザーIDなどの情報を保存できる。
アプリケーションはcookies内のデータを使って、例えばログイン中のユーザーが所有する情報をデータベースから取り出すことができる。
セッションをRESTfulなリソースとしてモデリングできると、他のRESTfulリソースと統一的に理解できて便利。
ログインページではnewで新しいセッションを出力し、そのページでログインするとcreateでセッションを実際に作成して保存し、ログアウトするとdestroyでセッションを破棄する、といった具合。
ただしUsersリソースと異なるのは、UsersリソースではバックエンドでUserモデルを介してデータベース上の永続的データにアクセスするのに対し、Sessionリソースでは代わりにcookiesを保存場所として使う点。
ログインの仕組みの大半は、cookiesを使った認証メカニズムによって構築されている。
Sessionsコントローラ
ログインとログアウトの要素を、Sessionsコントローラの特定のRESTアクションにそれぞれ対応付ける。
ログインのフォームは、newアクションで処理する。
createアクションにPOSTリクエストを送信すると、実際にログインする。
destroyアクションにDELETEリクエストを送信すると、ログアウトする。
Sessionsコントローラを生成する
Sessionsコントローラとnewアクションを生成する。
rails generate controller Sessions new
rails generateでnewアクションを生成すると、それに対応するビューも生成される。
createやdestroyには対応するビューが必要ないので、無駄なビューを作成しないためにここではnewだけを指定している。
Usersリソースのときは専用のresourcesメソッドを使ってRESTfulなルーティングを自動的にフルセットで利用できるようにしたが、Sessionリソースではフルセットはいらないので、「名前付きルーティング」だけを使う。
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new' # loginフォーム
post '/login', to: 'sessions#create' # login
delete '/logout', to: 'sessions#destroy' # loggout
resources :users
end
ルーティングと同様に、テストを更新し、新しいログイン用の名前付きルートを使うようにする必要がある。
require 'test_helper'
class SessionsControllerTest < ActionDispatch::IntegrationTest
test "should get new" do
get login_path # login_pathに変更
assert_response :success
end
end
HTTPリクエスト | URL | 名前付きルート | アクション名 | 用途 |
---|---|---|---|---|
GET | /login | login_path | new | 新しいセッションのページ (ログイン) |
POST | /login | login_path | create | 新しいセッションの作成 (ログイン) |
DELETE | /logout | logout_path | destroy | セッションの削除 (ログアウト) |
セッションルールによって提供されるルーティング
ログイン
今回扱うセッションはActive Recordオブジェクトではないので、ユーザー登録フォームのようにActive Recordがよしなにエラーメッセージを表示してくれるということは期待できない。
そこで今回は、フラッシュメッセージでエラーを表示する。
<% provide(:title, "Log in") %>
<h1>Log in</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(:session, url: login_path) 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>
# ユーザー登録フォーム
form_for(@user)
# ログインフォーム
form_for(:session, url: login_path)
ユーザー登録フォームではform_forヘルパーを使い、ユーザーのインスタンス変数@userを引数にとっていたが、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもないため、リソースの名前とそれに対応するURLを具体的に指定する必要がある(ユーザー登録フォームの場合は上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定していた)。
ユーザーの検索
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
# エラーメッセージを作成する
render 'new'
end
end
def destroy
end
end
user = User.find_by(email: params[:session][:email].downcase)
送信されたメールアドレスを使って、データベースからユーザーを取り出している。
user && user.authenticate(params[:session][:password])
&&(論理積(and)) は、取得したユーザーが有効かどうかを決定するために使う。
Rubyではnilとfalse以外のすべてのオブジェクトは、真偽値ではtrueになる。
そのため、入力されたメールアドレスを持つユーザーがデータベースに存在し、かつ入力されたパスワードがそのユーザーのパスワードである場合のみ、if文がtrueになる(「ユーザーがデータベースにあり、かつ、認証に成功した場合にのみ」)。
User | Password | a && b |
---|---|---|
存在しない | 何でもよい | (nil && [オブジェクト]) == false |
有効なユーザー | 誤ったパスワード | (true && false) == false |
有効なユーザー | 正しいパスワード | (true && true) == true |
ログイン失敗時のエラーメッセージ
前述のように、セッションではActive Recordのモデルを使っていないため、ログインに失敗したときには代わりにフラッシュメッセージを表示する。
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[:danger] = 'Invalid email/password combination' # 本当は正しくない
render 'new'
end
end
def destroy
end
end
実は上のコードのままでは、リクエストのフラッシュメッセージが一度表示されると消えずに残ってしまう。
ユーザー登録フォームでリダイレクトを使ったときとは異なり、表示したテンプレートをrenderメソッドで強制的に再レンダリングしてもリクエストと見なされないため、リクエストのメッセージが消えない。
例えばわざと無効な情報を入力して送信してエラーメッセージを表示してから、Homeページをクリックして移動すると、そこでもフラッシュメッセージが表示されたままになっている。
フラッシュのテスト
フラッシュメッセージが消えない問題は、テストのガイドラインに従えば、「エラーをキャッチするテストを先に書いて、そのエラーが解決するようにコードを書く」に該当する状況。
さっそく、ログインフォームの送信について簡単な統合テストを作成することから始めていく。
この統合テストは、そのままバグのドキュメントにもなり、今後の回帰バグ発生を防止する効能もある。
さらに、今後この統合テストを土台として、より本格的な統合テストを作成するときにも便利。
前述した問題をテストコードで再現する必要がある。
基本的な流れを次に示す。
- ログイン用のパスを開く
- 新しいセッションのフォームが正しく表示されたことを確認する
- わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
- 新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
- 別のページ (Homeページなど) にいったん移動する
- 移動先のページでフラッシュメッセージが表示されていないことを確認する
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
test "login with invalid information" do
get login_path # 1
assert_template 'sessions/new' # 2
post login_path, params: { session: { email: "", password: "" } } # 3
assert_template 'sessions/new' # 4
assert_not flash.empty? # 4
get root_path # 5
assert flash.empty? # 6
end
end
flash.now
レンダリングが終わっているページで特別にフラッシュメッセージを表示することができる。
flashのメッセージとは異なり、flash.nowのメッセージはその後リクエストが発生したときに消滅する。
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'
render 'new'
end
end
def destroy
end
end
ログイン
cookiesを使った一時セッションでユーザーをログインできるようにする。
このcookiesは、ブラウザを閉じると自動的に有効期限が切れるものを使う。
セッションを実装するには、様々なコントローラやビューでおびただしい数のメソッドを定義する必要がある。
Rubyのモジュール機能を使うと、そうしたメソッドを一箇所にパッケージ化できる。
Sessionsコントローラを生成した時点で既にセッション用ヘルパーモジュールも (密かに) 自動生成されている。
さらに、Railsのセッション用ヘルパーはビューにも自動的に読み込まれる。
Railsの全コントローラの親クラスであるApplicationコントローラにこのモジュールを読み込ませれば、どのコントローラでも使えるようになる。
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper # Sessionモジュールを読み込ませる
end
log_inメソッド
同じログイン手法を様々な場所で使い回せるようにするために、Sessionsヘルパーにlog_inという名前のメソッドを定義する。
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
end
sessionメソッド
このコードを実行すると、ユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成される。
sessionメソッドで作成された一時cookiesは、ブラウザを閉じた瞬間に有効期限が終了する。
sessionメソッドで作成した一時cookiesは自動的に暗号化され、このコードは保護される。
ここで重要なことは、攻撃者がたとえこの情報をcookiesから盗み出すことができたとしても、それを使って本物のユーザーとしてログインすることはできないこと。
ただし今述べたことは、sessionメソッドで作成した「一時セッション」にしか該当しない(cookiesメソッドで作成した「永続的セッション」ではそこまで断言はない。永続的なcookiesには、セッションハイジャックという攻撃を受ける可能性が常につきまとう)。
session[:user_id] = user.id
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 # redirect_to user_url(user)に変換される
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
end
end
current_userメソッド
ユーザーIDを一時セッションの中に安全に置けるようになったので、そのユーザーIDを別のページで取り出す。
def current_user
User.find_by(id: session[:user_id])
end
find_byメソッド
ユーザーIDが存在しない状態でfindを使うと例外が発生してしまう。
findのこの動作は、プロフィールページでは適切だった(IDが無効の場合は例外を発生してくれなければ困るから)。
しかし、「ユーザーがログインしていない」などの状況が考えられる今回のケースでは、session[:user_id]の値はnilになりえる。
この状態を修正するために、createメソッド内でメールアドレスの検索に使ったのと同じfind_byメソッドを使う。
find_byメソッドは、IDが無効な場合 (=ユーザーが存在しない場合) にもメソッドは例外を発生せず、nilを返す。
User.find_by(id: session[:user_id])
current_userメソッドの改良
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
# 現在ログイン中のユーザーを返す (いる場合)
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
end
# 1
def current_user
User.find_by(id: session[:user_id])
end
# 2
def current_user
if @current_user.nil?
@current_user = User.find_by(id: session[:user_id])
else
@current_user
end
end
# 3
def current_user
@current_user = @current_user || User.find_by(id: session[:user_id])
end
# 4
def current_user
@current_user ||= User.find_by(id: session[:user_id])
end
- 正常に動作はする。
- current_userメソッドが1リクエスト内で何度も呼び出されると、呼び出された回数と同じだけデータベースにも問い合わせされてしまう。そこでRubyの慣習に従って、User.find_byの実行結果をインスタンス変数に保存することにする。こうすることで、データベースの読み出しは最初の一回だけになり、以後の呼び出しではインスタンス変数を返すようになる。地味なようだが、Railsを高速化させるための重要なテクニック。
- or演算子「||」を使えれば、2の「メモ化」コードがたった1行で書ける。
- 「||=(or equals)記法で短縮化する。
「||=」(or equals)
「||=」(or equals)という代入演算子はRubyで広く使われているイディオム。
Rubyでは、「変数の値がnilなら変数に代入するが、nilでなければ代入しない(変数の値を変えない)」という操作が非常によく使われる。
or equalsという概念は一見不思議にみえるが、他のものになぞらえて考えれば難しくない。
x = x + 1 -> x += 1
x = x * 3 -> x *= 3
x = x - 8 -> x -= 8
x = x / 2 -> x /= 2
@foo = @foo || "bar" -> @foo ||= "bar"
logged_in?メソッド
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
# 現在ログイン中のユーザーを返す (いる場合)
def current_user
@current_user ||= User.find_by(id: session[:user_id])
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>
ドロップダウン機能を有効にする
ドロップダウン機能を有効にするため、Railsのapplication.jsファイルを通して、Bootstrapに同梱されているJavaScriptライブラリとjQueryを読み込むようアセットパイプラインに指示する。
//= require rails-ujs
//= require jquery # jQueryを読み込む
//= require bootstrap # Bootstrapに同梱されているJavaScriptライブラリを読み込む
//= require turbolinks
//= require_tree .
ログインの有無でのレイアウトの変更のテスト
- ログイン用のパスを開く
- セッション用パスに有効な情報をpostする
- ログイン用リンクが表示されなくなったことを確認する
- ログアウト用リンクが表示されていることを確認する
- プロフィール用リンクが表示されていることを確認する
ログイン成功時のレイアウトの変更を確認するためには、テスト時に登録済みユーザーとしてログインしておく必要がある。
当然ながら、データベースにそのためのユーザーが登録されていなければならない。
Railsでは、このようなテスト用データをfixture (フィクスチャ) で作成できる。
このfixtureを使って、テストに必要なデータをtestデータベースに読み込んでおくことができる。
digestメソッド
テスト中にそのユーザーとして自動ログインするために、そのユーザーの有効なパスワードも用意して、Sessionsコントローラのcreateアクションに送信されたパスワードと比較できるようにする必要がある。
Userモデルを見ると、password_digest属性をユーザーのfixtureに追加すればよいことが分かる。
そのために、digestメソッドを独自に定義することにする。
has_secure_passwordでbcryptパスワードが作成されるので、同じ方法でfixture用のパスワードを作成する。
Railsのsecure_passwordのソースコードを調べてみると、次の部分でパスワードが生成されていることが分かる。
BCrypt::Password.create(string, cost: cost)
上のstringはハッシュ化する文字列、costはコストパラメータと呼ばれる値。
コストパラメータでは、ハッシュを算出するための計算コストを指定する。
コストパラメータの値を高くすれば、ハッシュからオリジナルのパスワードを計算で推測することが困難になりますので、本番環境ではセキュリティ上重要。
しかしテスト中はコストを高くする意味はないので、digestメソッドの計算はなるべく軽くしておきたい。
この点についても、secure_passwordのソースコードには次の行が参考になる。
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
少々込み入っているが、コストパラメータをテスト中は最小にし、本番環境ではしっかりと計算する方法がわかれば十分。
このdigestメソッドは、今後様々な場面で活用するので、このdigestメソッドはUserモデル (user.rb) に置いておく。
この計算はユーザーごとに行う必要はないので、fixtureファイルなどでわざわざユーザーオブジェクトにアクセスする必然性はない(つまり、インスタンスメソッドで定義する必要がない)。
そこで、digestメソッドをUserクラス自身に配置して、クラスメソッドにすることにする (クラスメソッドの作り方についてはここで簡単に説明した)。
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: { case_sensitive: false }
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
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
上のコードにあるように、fixtureではERbを利用できる。
<%= User.digest('password') %>
上のERbコードでテストユーザー用の有効なパスワードを作成できる。
has_secure_passwordで必要となるpassword_digest属性はこれで準備できたが、ハッシュ化されていない生のパスワードも参照できると便利。
しかし残念なことに、fixtureではこのようなことはできない。
さらに、fixtureにpassword属性を追加すると、そのようなカラムはデータベースに存在しないというエラーが発生する。
実際、データベースにはそんなカラムはない。
この状況を切り抜けるため、テスト用のfixtureでは全員同じパスワード「password」を使うことにする (これはfixtureでよく使われる手法)。
テストの実装
有効なユーザー用のfixtureを作成できたので、テストでは次のようにfixtureのデータを参照できるようになる。
user = users(:michael)
上のusersはfixtureのファイル名users.ymlを表し、:michaelというシンボルはユーザーを参照するためのキーを表す。
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
ユーザー登録時にログイン
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
log_in @user # ユーザー登録中にログインする
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
動作をテストするために、test/integration/users_signup_test.rbの"valid signup information"テストに1行追加して、ユーザーがログイン中かどうかをチェックする。
そのために、logged_in?ヘルパーメソッドとは別に、is_logged_in?ヘルパーメソッドを定義しておくと便利。
このヘルパーメソッドは、テストのセッションにユーザーがあればtrueを返し、それ以外の場合はfalseを返す。
残念ながらヘルパーメソッドはテストから呼び出せないので、current_userを呼び出せない。
sessionメソッドはテストでも利用できるので、これを代わりに使う。
ここでは取り違えを防ぐため、logged_in?の代わりにis_logged_in?を使って、ヘルパーメソッド名がテストヘルパーとSessionヘルパーで同じにならないようにしておく。
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
ログアウト
これまで、SessionsコントローラのアクションはRESTfulルールに従っていた。
newでログインページを表示し、createでログインを完了するといった具合。
セッションを破棄するdestroyアクションも、引き続き同じ要領で作成することにする。
ただし、ログインの場合と異なり、ログアウト処理は1か所で行えるので、destroyアクションに直接ログアウト処理を書くことにする。
この設計 (および若干のリファクタリング) のおかげで認証メカニズムのテストが行い易くなる。
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
.
.
.
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id) # 現在のユーザーをnilに設定する
@current_user = nil
end
end
ここで定義したlog_outメソッドは、Sessionsコントローラのdestroyアクションでも同様に使っていく。
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 # セッションを破棄する (ユーザーのログアウト)
log_out
redirect_to root_url
end
end
ログアウト機能のテスト
ログアウト機能をテストするために、test/integration/users_login_test.rbの"login with valid information"テスト(ユーザーログインテスト)に手順を若干追加する。
ログイン後、deleteメソッドでDELETEリクエストをログアウト用パスに発行し、ユーザーがログアウトしてルートURLにリダイレクトされたことを確認する。
ログイン用リンクが再度表示されること、ログアウト用リンクとプロフィール用リンクが非表示になることも確認する。
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
.
.
.
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! # root urlにリダイレクト
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
セッションのdestroyアクションの定義とテストも完成したので、ついにサンプルアプリケーションの基本となる「ユーザー登録・ログイン・ログアウト」の機能すべてが完成した。
参考文献
Rubyで任意のメソッドをメモ化する
【HTML】ページ内リンクの作り方:記事の途中に飛ばすには?
GoogleChromeで特定のサイトのクッキー(Cookie)情報を確認する方法