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

Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #7 ログイン機能の下準備編

こんな人におすすめ

  • プログラミング初心者でポートフォリオの作り方が分からない
  • Rails Tutorialをやってみたが理解することが難しい

前回:#6 ユーザー登録画面, エラー日本語化編
次回:#8 ログイン/ログアウト, FactroyBot編

今回の流れ

  1. 今回のゴールを把握する
  2. ログイン画面のビューを作る
  3. Sessionsコントローラーを作る
  4. ログイン画面のテストを作る

※ この記事は、ポートフォリオを作る理由をweb系自社開発企業に転職するためとします。
※ 2020年4月2日、記事を大幅に更新しました。

今回のゴールを把握する

ゴールは、ログイン画面の外見だけを作ることです。
実際にログインさせる機能は、#8で行います。

まずは、ログイン画面のビューを作ります。
続いて、ログインを管理するSessionsコントローラーを作ります。
最後に、ログイン画面のテストを作ります。

以上です。

ログイン画面のビューを作る

ログイン画面のビューを作ります。
突然ですが、ジェネレーターでコントローラーを生成するとビューも生成されます。
後ほどコントローラーが必要なので、ジェネレーターではコントローラーを選択します。

shell
$ rails g controller Sessions new

ビュー作成前に、以下の2つについて解説します。

  • form_for(:session, url: login_path)というRails Tutorialの記述について
  • form_forからform_withの書き換えについて

form_for(:session, url: login_path)について

form_for(:session, url: login_path)という、Rails Tutorialの記述について考えます。
まずは、Rails Tutorialによるこちらをご覧ください。

form_for(@user)

Railsでは上のように書くだけで、「フォームのactionは/usersというURLへのPOSTである」と自動的に判定しますが、セッションの場合はリソースの名前とそれに対応するURLを具体的に指定する必要があります。

form_for(:session, url: login_path)

つまり、以下のような違いが生まれます。

form_for(@user) → /usersにparams[:user]として情報を渡す
form_for(:session, url: login_path) → /loginにparams[:session]として情報を渡す

リソース名を'session'にすることで、params[:session]にキーと値が保存されます。

参考になりました↓
rails ログイン機能のform_forの作りについての疑問
[Rails4.0] フォームの基本とStrongParametersを理解する

form_forからform_withの書き換えについて

form_forは非推奨のため、form_withを使います。
その際はリソース名をscopeに指定する必要があります。
よって、以下のような記述になります。

<%= form_with(scope: :session, url: login_path, local: true) do |form| %>

改めてログイン画面のビューを作る

以上2点を参考に、改めてビューを作ります。

app/views/sessions/new.html.erb
<% provide(:title, "ログイン") %>
<div class="container form-container login-container">
  <div class="row">
    <div class="col">
      <div class="form-logo-img">
        <%= link_to image_tag('lantern_lantern_logo.png', width: 100), root_path, class: "logo-img" %>
      </div>
      <h1 class="form-title">ログイン</h1>
      <%= form_with(scope: :session, url: login_path, local: true) do |form| %>

        <div class="form-group">
          <%= form.email_field :email, class: 'form-control', placeholder: "メールアドレス" %>
        </div>

        <div class="form-group">
          <%= form.password_field :password, class: 'form-control', placeholder: "パスワード" %>
        </div>

        <div class="form-group">
          <%= form.submit "ログイン", class: "btn btn-info btn-lg form-submit" %>
        </div>
      <% end %>

      <p class="form-go-to-signup-or-login">新しくはじめる方は<%= link_to "こちら", signup_path %></p>
    </div>
  </div>
</div>

ヘッダーも修正します。
'Login'ボタンに、リンク先を設定します。

app/views/layouts/_header.html.erb
<!-- 中略 -->
<div id="menu" class="collapse navbar-collapse">
  <ul class="navbar-nav ml-auto">
    <li class="nav-item">
      <%= link_to "Login", login_path, class: "btn btn-info btn-md" %>
    </li>
  </ul>
</div>
<!-- 中略 -->

参考になりました↓
【Ruby】チュートリアルのform_forをform_withで書き換え (おまけ:capybaraでのテスト)

Sessionsコントローラーを作る

ログインを管理するSessionsコントローラーを作ります。
Sessionsコントローラーは、Rails Tutorial 8.1 セッションと同じです。

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] = 'メールアドレスかパスワードが正しくありません'
      render 'new'
    end
  end

  def destroy
    # 次回:ここに、ログアウトさせる記述を加える
  end
end

createアクションに関する、以下の2つについて解説します。

  • userがインスタンス変数でないことについて
  • どのようにif文が成立するかについて

1つ目は、今回createアクションに対するビューを作りません。
フォーム送信後、ログイン画面に戻すか、ユーザーページに遷移します。
よってビューがないため、ビューに引き渡すuser情報が不要です。
したがって、インスタンス変数である必要がありません。

2つ目は、nilとfalse以外のオブジェクトはtrueになる、という性質を利用しています。
userが存在する場合は、nilではないためtrueになります。
authenticateは、パスワードが正しい場合trueになります。
つまりif文は、userが存在しフォームからのパスワードが正しい時、という意味になります。

ログイン画面のテストを作る

ログイン画面のテストを作ります。
Tutorial 8.1.5 フラッシュのテストを参考にします。

bash
$ rails g rspec:request users_login
$ touch spec/systems/login_spec.rb
spec/requests/users_logins_spec.rb
require 'rails_helper'

RSpec.describe "UsersLogins", type: :request do

  let(:user) { create(:user) }

  describe "GET /login" do
    it "has a danger flash message because of invalid login information" do
      get login_path
      post login_path, params: {
        session: {
          email: "",
          password: ""
        }
      }
      expect(flash[:danger]).to be_truthy
    end

    it "has no danger flash message because of valid login information" do
      get login_path
      post login_path, params: {
        session: {
          email: user.email,
          password: user.password
        }
      }
      expect(flash[:danger]).to be_falsey
    end
  end
end
spec/systems/login_spec.rb
require 'rails_helper'

RSpec.describe "Logins", type: :system do

  let(:user) { create(:user) }

  context "login with invalid information" do
    it "is invalid because it has no information" do
      visit login_path
      expect(page).to have_selector '.login-container'
      fill_in 'メールアドレス', with: ''
      fill_in 'パスワード', with: ''
      find(".form-submit").click
      expect(current_path).to eq login_path
      expect(page).to have_selector '.login-container'
      expect(page).to have_selector '.alert-danger'
    end

    it "deletes flash messages when users input invalid information then other links" do
      visit login_path
      expect(page).to have_selector '.login-container'
      fill_in 'メールアドレス', with: ''
      fill_in 'パスワード', with: ''
      find(".form-submit").click
      expect(current_path).to eq login_path
      expect(page).to have_selector '.login-container'
      expect(page).to have_selector '.alert-danger'
      visit root_path
      expect(page).not_to have_selector '.alert-danger'
    end
  end

  context "login with valid information" do
    it "is valid because it has valid information" do
      visit login_path
      fill_in 'メールアドレス', with: user.email
      fill_in 'パスワード', with: 'password'
      find(".form-submit").click
      expect(current_path).to eq user_path(1)
      expect(page).to have_selector '.show-container'
    end
  end
end

今回は以上です。


<追記>
ログイン成功時のフラッシュメッセージの有無の確認を行う場合、事前にユーザがActiveRecord内に存在する必要があります。
そのためにFactoryBotを使いますが、#7ではまだ導入していません。
#8で導入します。)

よってテストは失敗します。
#7では以下のブロックをコメントアウトするとテストが通ります。

# let(:user) { create(:user) }
# it "has no danger flash message because of valid login information" do
# context "login with valid information" do

参考になりました↓
Ruby on Rails ガイド 5.2 Flash


前回:#6 ユーザー登録画面, エラー日本語化編
次回:#8 ログイン/ログアウト, FactroyBot編

aokyo17
rails tutorial → ポートフォリオing. 誰もが経験した初心びくびく20代1年目.. フォローはすぐ返したい厨。
https://komucha.com/
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