Help us understand the problem. What is going on with this article?

RailsTutorialの記録と備忘録 #13 『ログイン失敗処理』

ログインページではnewで新しいセッションを出力し、そのページでログインするとcreateでセッションを実際に作成して保存し、ログアウトするとdestroyでセッションを破棄する。

ユーザーログインはsessionを使って行う。
cookiesは、ユーザーのブラウザに保存されるテキストデータ。
cookiesは、あるページから別のページに移動した時にも破棄されないため、ユーザーIDなどの情報を保存できる。

Sessionsコントローラー作成

rails generate controller Sessions new

ログイン、ログアウト用のルーティングを追加

routes.rb
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

テストで名前付きルートを使うように変更

test/controllers/sessions_controller_test.rb
require 'test_helper'

class SessionsControllerTest < ActionDispatch::IntegrationTest

  test "should get new" do
    get login_path
    assert_response :success
  end
end

rails routesコマンドでルーティングをリストできる。

ログインフォーム作成

アドレスとパスワードが違ったらログインページを再度表示し、flashメッセージを表示する。

/usersにフォームをPOSTする場合、
form_for(@user)とすると自動的に判定される。
セッションの場合はリソースの名前とそれに対応するURLを具体的に指定する必要がある。

form_for(:session, url: login_path)

ログインページ

app/views/sessions/new.html.erb
<% 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>

Sessionsコントローラにログイン機能を実装

sessionから情報を取り出してログイン。

find_byでemailからユーザーを検索
authenticateで認証する。

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])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
  end

  def destroy
  end
end

どうやらform_forはparam[ARG1]に値をセットするもので、:sessionじゃなくてもいいっぽい。
form_for :papipuにしたらparams[:papipu]で取れる。

nilじゃないオブジェクトはtrueを返すので、
if user && user.authenticate(params[:session][:password])
は「ユーザーが存在し、かつ認証が成功したら」

flashでログインエラーを表示

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])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash[:danger] = 'Invalid email/password combination' # 本当は正しくない
      render 'new'
    end
  end

  def destroy
  end
end

この状態だと別ページに移動したとき、flashが消えない。

表示したテンプレートをrenderメソッドで強制的に再レンダリングしてもリクエストと見なされないため、リクエストのメッセージが消えません。例えばわざと無効な情報を入力して送信してエラーメッセージを表示してから、Homeページをクリックして移動すると、そこでもフラッシュメッセージが表示されたままになっています。

よくわからないけどたしかに消えなかった。

flashのテスト

rails generate integration_test user_login
  1. ログイン用のパスを開く
  2. 新しいセッションのフォームが正しく表示されたことを確認する
  3. わざと無効なparamsハッシュを使ってセッション用パスにPOSTする
  4. 新しいセッションのフォームが再度表示され、フラッシュメッセージが追加されることを確認する
  5. 別のページ (Homeページなど) にいったん移動する
  6. 移動先のページでフラッシュメッセージが表示されていないことを確認する
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

flashflash.nowに修正

後者は、レンダリングが終わっているページで特別にフラッシュメッセージを表示することができます。

flash.nowは表示した後に再度リクエストされても消えないらしい。

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])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
  end

  def destroy
  end
end
kide
主に備忘録を書いていきます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした