こんな人におすすめ
- プログラミング初心者でポートフォリオの作り方が分からない
- Rails Tutorialをやってみたが理解することが難しい
前回:#6 ユーザー登録画面, エラー日本語化編
次回:#8 ログイン/ログアウト, FactroyBot編
今回の流れ
- 今回のゴールを把握する
- ログイン画面のビューを作る
- Sessionsコントローラーを作る
- ログイン画面のテストを作る
※ この記事は、ポートフォリオを作る理由をweb系自社開発企業に転職するためとします。
※ 2020年4月2日、記事を大幅に更新しました。
今回のゴールを把握する
ゴールは、ログイン画面の外見だけを作ることです。
実際にログインさせる機能は、#8で行います。
まずは、ログイン画面のビューを作ります。
続いて、ログインを管理するSessionsコントローラーを作ります。
最後に、ログイン画面のテストを作ります。
以上です。
ログイン画面のビューを作る
ログイン画面のビューを作ります。
突然ですが、ジェネレーターでコントローラーを生成するとビューも生成されます。
後ほどコントローラーが必要なので、ジェネレーターではコントローラーを選択します。
$ 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点を参考に、改めてビューを作ります。
<% 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'ボタンに、リンク先を設定します。
<!-- 中略 -->
<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 セッションと同じです。
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 フラッシュのテストを参考にします。
$ rails g rspec:request users_login
$ touch spec/systems/login_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
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