セッション
$ rails generate controller Sessions new
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'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
end
new 新しいセッションのページ
create 新しいセッションの作成 (ログイン)
セッションの削除 (ログアウト)
require 'test_helper'
class SessionsControllerTest < ActionDispatch::IntegrationTest
test "should get new" do
get login_path
assert_response :success
end
end
ログインフォーム
ログインフォームとユーザー登録フォームにはほとんど違いがない
<% 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>
ユーザーの検索と認証
class SessionsController < ApplicationController
def new
end
def create
render 'new'
end
def destroy
end
end
createアクションの中では、ユーザーの認証に必要なあらゆる情報をparamsハッシュから簡単に取り出せる
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
フラッシュメッセージを表示する
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
フラッシュのテスト
1ログイン用のパスを開く
2新しいセッションのフォームが正しく表示されたことを確認する
3わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
4新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
5別のページ (Homeページなど) にいったん移動する
6移動先のページでフラッシュメッセージが表示されていないことを確認する
上のテスト手順の実装
$ rails generate integration_test users_login
invoke test_unit
create test/integration/users_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
test "login with invalid information" do
get login_path
assert_template 'sessions/new'
post login_path, params: { session: { email: "", password: "" } }
assert_template 'sessions/new'
assert_not flash.empty?
get root_path
assert flash.empty?
end
end
上テストは失敗する
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は、ブラウザを閉じると自動的に有効期限が切れるものを使います
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
sessionメソッドを使って、単純なログインを行えるようにします
コードを実行すると、ユーザーのブラウザ内の一時cookiesに暗号化済みのユーザーIDが自動で作成されます。この後のページで、session[:user_id]を使ってユーザーIDを元通りに取り出すことができます
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
end
end
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
現在のユーザー
ユーザーIDが存在しない状態でfindを使うと例外が発生してしまいます、なので
find_byメソッドを使うことにします。
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
@current_user = @current_user || User.find_by(id: session[:user_id])
省略
@current_user ||= User.find_by(id: session[:user_id])
レイアウトリンクを変更する
。
。
。
# ユーザーがログインしていれば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>
<%= link_to "Log out", logout_path, method: :delete %>
上のコードでは、ログアウト用リンクの引数としてハッシュを渡している点にご注目ください。このハッシュでは、HTTPのDELETEリクエストを使うように指示しています7。プロフィール用リンクについても同様に、次のように変更します
Bootstrapのドロップダウンメニュー機能8を適用できる状態になりました
//= require rails-ujs
//= require jquery ←ここ追加
//= require bootstrap ←ここも
//= require turbolinks
//= require_tree .
レイアウトの変更をテストする
1ログイン用のパスを開く
2セッション用パスに有効な情報をpostする
3ログイン用リンクが表示されなくなったことを確認する
4ログアウト用リンクが表示されていることを確認する
5プロフィール用リンクが表示されていることを確認する
テスト時に登録済みユーザーとしてログインしておく必要があります。当然ながら、データベースにそのためのユーザーが登録されていなければなりません。Railsでは、このようなテスト用データをfixture (フィクスチャ) で作成できます。
.
.
.
# 渡された文字列のハッシュ値を返す
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') %>
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
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
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
ログアウト
destroyアクションに直接ログアウト処理を書く
.
.
.
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id)
@current_user = nil
end
end
.
.
def destroy
log_out
redirect_to root_url
end
end
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!
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
プッシュして8章終わり