Edited at

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


こんな人におすすめ


  • プログラミング初心者でポートフォリオの作り方が分からない

  • Rails Tutorialをやってみたが理解することが難しい

  • ポートフォリオを作成しながら勉強したい

前回:#6 サインアップ, エラー日本語化編

次回:#8 ログイン/ログアウト, FactroyBot編


こんなことが分かる


  • ログインの下準備をする方法

  • form_for/form_withの値の保存先について

一緒に勉強しよぅ:bow:


ログインまでの色々を実装する

Tutorialは8.1 セッションに突入。

これからログインするまでのあれこれを実装していこう:thumbsup:

流れとしては下記の3つを行う。


  • コントローラ生成

  • ビュー生成

  • それらのテスト生成

機能としてのログインは次回#8。


ログインのためのコントローラ生成

とりあえずTutorial通りにSessionsコントローラを生成する。

くどいけどテストはRequest specで書く。


bash

$ rails g controller Sessions new

$ rails g rspec:request session


spec/requests/sessions_spec.rb

require 'rails_helper'

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

describe "GET /login" do
it "returns http success" do
get login_path
expect(response).to have_http_status(:success)
end
end
end


あとはホーム画面の'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>
<!-- 中略 -->

以前failureとなったテストがようやくこれで通る。

ではSessionsコントローラを実装しよう。


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


createアクションにこんな記述があると思うけど、

if user && user.authenticate(params[:session][:password])

Tutorialではこんな風に述べてくれている。


Rubyではnilとfalse以外のすべてのオブジェクトは、真偽値ではtrueになる


だからUserクラスが真偽値っぽくなくてもif文が通るのね。


あと何気にuserがインスタンスじゃないのも特徴。

createアクションに対するビューとかが必要なく(ログインできましたよ!みたいな画面を別で作るなら考慮してよい)アクション内で収まるのでインスタンスでない。


ログインのためのビュー生成


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

続いてログイン画面を作ろうと思うんだけど、Tutorialでこんな記述がある。


form_for(@user)

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

form_for(:session, url: login_path)


なにこれ??最初は意味が分かりませんでした。

で調べたわけだけど、リソース名を'session'にすることでparams[:session]にキーと値が保存されるのね。

つまり渡しているのはそれぞれこう。

form_for(@user)  params[:user]

form_for(:session, url: login_pash) params[:session]

このあたりはこちらが分かりやすい!↓

rails ログイン機能のform_forの作りについての疑問

[Rails4.0] フォームの基本とStrongParametersを理解する


form_withで書き換える

このポートフォリオではform_forではなくform_withを使う(理由は#6)。

その際はリソース名をscopeに指定するので、最終的にはこうなる。


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>


書き換えでお世話になりました↓

【Ruby】チュートリアルのform_forをform_withで書き換え (おまけ:capybaraでのテスト)


ログインのテスト生成

Tutorial 8.1.5 フラッシュのテストにならってテストを書こう。

Tutorialとの差分も用意する。


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(RSpec:ポートフォリオ)

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



test/integration/users_login_test.rb(Minitest:Tutorial)

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


System specでの大きな変更点は5つ。


  1. テストは1つから2つに

  2. getはvisitに

  3. assert_templateはhave_selectorに

  4. postはfill_inに

  5. flash_empty?はhave_selectorに

1.

・フォームに入力しない場合エラーが発生する

・その場合別ページでflashが消える

を独立させたかったので2つに分けた。

2.

System specでgetやpostを指定するとうまくいかない。

代わりにvisitを使おう。


追記

2.の理由はSystem specがブラウザ上のテストだからです。

(詳しくは#8で解き明かされます)


3.

assert_templateは動作が確認できません。

(Capybaraのサポート終了?)

have_selectorを使用しdivを検証することで描画をテストする。

4.

ブラウザテストの性格上、直接postをリクエストするのではなくフォームに値を入力するのが良い。fill_inを使う。

5.

flashに対してbe_emptyを使用したテストは実装できなかった。

System specではこういう書き方もできない。

expect(flash[:danger]).to be_truthy


bash

NoMethodError:

undefined method `flash' for nil:NilClass

Request specと異なりSystem specはコントローラで使用できるFlashHashオブジェクトと紐づいていないっぽい??

(ここも#8

代わりにalert時に生成されるdiv要素を検証した。

参考にさせていただきました↓

Ruby on Rails ガイド 5.2 Flash


追記

ログイン成功時のフラッシュメッセージの有無の確認を行う場合、事前にユーザが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


次回はいよいよ機能としてのログイン実装

おそらく次回がログインのメインです。

Sessionsヘルパーに各メソッドを与えます。

(超初心者向け)解説が載っているのでぜひご参考に〜:stuck_out_tongue_closed_eyes:


追記

[2019年8月17日]

記事改変しました。

前回:#6 サインアップ, エラー日本語化編

次回:#8 ログイン/ログアウト, FactroyBot編