###user.rb
user.rb
class User < ApplicationRecord
# 継承させる
attr_accessor :remember_token
before_save { self.email = email.downcase }
# 保存する前に メアドは小文字にする
validates :name, presence: true, length: { maximum: 50 }
# nameカラムが存在する
# validates(:name, presence: true)
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
# 正規表現
validates :email, presence: true, length: { maximum: 255 },
# 存在性、長さの検証
format: { with: VALID_EMAIL_REGEX },
# メールフォーマットの検証
uniqueness: { case_sensitive: false }
# :case_sensitive 大文字と小文字を区別しないで一意性を検証する
has_secure_password
# 仮想的なpassword属性とpassword_confirmation属性に対してバリデーションをする機能も(強制的に)追加
validates :password, presence: true, length: { minimum: 6 }
# 最低でも六文字異常 存在性のある文字列をパスワードととする。
# 渡された属性が存在していることを検証する
# 渡された文字列のハッシュ値を返す
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
# コストパラメータはテストでは最小にする
BCrypt::Password.create(string, cost: cost)
# stringはハッシュ化する文字列
# cost ハッシュ化する計算コストを表す
end
# ランダムなトークンを返す
def User.new_token
SecureRandom.urlsafe_base64
end
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
self.remember_token = User.new_token
# 新しいトークンを 記憶トークンにする
update_attribute(:remember_digest, User.digest(remember_token))
# 記憶ダイジェストの欄に記憶トークンを基にしたダイジェストを書き込む
end
# 渡されたトークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
return false if remember_digest.nil?
# 結果がfalseの時に 記憶ダイジェストが空かどうか確かめる。
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
# ユーザーのログイン情報を破棄する
def forget
update_attribute(:remember_digest, nil)
# 記憶ダイジェストを空に上書きする
end
end
###sessions_controller.rb
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])
# ユーザーログイン後にユーザー情報のページにリダイレクトする
# ユーザーの持つパスワードが認識されるか
# 有効且つパスワードを認識する
log_in user
# ログインさせる
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
# 1はtureを表す
# sessionとremember_meが有効ならば remember()でなければforget()を行う
# forget() ユーザー情報を削除する
# remember() ユーザー情報を記憶する
# user.rbのrememberメソッドを使用する
# ログインしてユーザーを保持する
redirect_to user
# user_url(user)を表す
# プロフィールページへのルーティングにしています。
else
flash.now[:danger] = 'Invalid email/password combination' # 本当は正しくない
# エラーメッセージを作成する
#.nowで何かすると消えるようにする
render 'new'
end
end
def destroy
# セッションの削除(ログアウト)
log_out if logged_in?
# ログイン中だったらログアウトする
redirect_to root_url
# 削除した後 ホーム画面に戻る
end
end
###sessions_helper.rb
sessions_helper.rb
module SessionsHelper
# 渡されたユーザーでログインする
def log_in(user)
session[:user_id] = user.id
# sessionはログインを表す
end
# ユーザーのセッションを永続的にする
def remember(user)
user.remember
# トークンを生成して記憶トークンにする
cookies.permanent.signed[:user_id] = user.id
# 永続化クッキー(有効期間が20年に設定されたクッキー)を設定
# cookies.permanent[クッキー名] = 値
# 署名付きクッキーを設定
# cookies.signed[クッキー名] = 値
cookies.permanent[:remember_token] = user.remember_token
end
# 現在ログイン中のユーザーを返す(いる場合)
def current_user
if session[:user_id]
# sessionsに含まれるなら
@current_user ||= User.find_by(id: session[:user_id])
# それをログイン中のユーザーcurrent_userと置く
end
end
# 記憶トークンcookieに対応するユーザーを返す
def current_user
if (user_id = session[:user_id])
# セッションに情報を保存
# session[キー] = 値
@current_user ||= User.find_by(id: user_id)
# idにuser_idをもとにデータベースから探す又はcurrent_userに代入する
elsif (user_id = cookies.signed[:user_id])
# 署名付きのクッキーを設定できたら
raise
# raise 発生させたい例外のクラス 以下
user = User.find_by(id: user_id)
# user_idを基に探す
if user && user.authenticated?(cookies[:remember_token])
# クッキーとは、クライアント側に保存されるファイル
# cookies[:クッキー名] = { key: クッキー情報 }
# userが有効且つクッキーが認証されているか?
log_in user
# ログイン
@current_user = user
# @current_userにする
end
end
end
# ユーザーがログインしていればtrue、その他ならfalseを返す
def logged_in?
# これで判定させて表示内容を決める
!current_user.nil?
# ログイン中のユーザーは空じゃないか確認
end
# 現在のユーザーをログアウトする
def log_out
session.delete(:user_id)
# sessionからidを削除する
@current_user = nil
# ログイン済みのユーザーを空にする
end
# 永続的セッションを破棄する
def forget(user)
# forgetメソッド
user.forget
# 記憶ダイジェストを空にする
cookies.delete(:user_id)
# クッキーをuser_idを基に削除する
cookies.delete(:remember_token)
# 記憶トークンを削除する
end
# 現在のユーザーをログアウトする
def log_out
forget(current_user)
# current_userのuser_idとremember_tokenを削除する
session.delete(:user_id)
# セッションから削除する
@current_user = nil
# current_userを空にする
end
end
###user_login_test.rb
user_login_test.rb
require 'test_helper'
class UsersLoginTest < ActionDispatch::IntegrationTest
def setup
# テストユーザー
@user = users(:michael)
end
test "login with valid email/invalid password" do
# ログインをするまでの過程をチェック
get login_path
# 名前付きルートでログイン画面に行く
assert_template 'sessions/new'
# 要求されたログイン画面が表示されたか?
post login_path, params: { session: { email: @user.email,
password: "invalid" } }
# ログイン画面に無効なテストデータを入力
assert_not is_logged_in?
# test_helper ログイン中でない
assert_template 'sessions/new'
# 失敗したらまたログインページが表示されるか?
assert_not flash.empty?
# flashメッセージは表示されたか?
get root_path
# ホーム画面に行くことを要求する?
assert flash.empty?
# flashメッセージは表示されなかったか?
end
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
# 2番目のウィンドウでログアウトをクリックするユーザーをシミュレートする
delete logout_path
# 削除されるかどうか
follow_redirect!
# リダイレクト実行後に続いて別のリクエストを行う予定があるのであれば、follow_redirect!を呼び出す
assert_select "a[href=?]", login_path
# login_pathが表示されているか?
assert_select "a[href=?]", logout_path, count: 0
# logout_pathが表示されていないか?
assert_select "a[href=?]", user_path(@user), count: 0
# user_path(@user)が表示されていないか?
end
test "login with remembering" do
log_in_as(@user, remember_me: '1')
# 有効なユーザー remember_meが有効
assert_not_empty cookies[:remember_token]
# クッキーとは、クライアント側に保存されるファイルのこと
# cookies[:クッキー名] = { key: クッキー情報 }
# cookiesが有効か?
end
test "login without remembering" do
# cookieを保存してログイン
log_in_as(@user, remember_me: '1')
# 有効なユーザー remember_meが有効 ログインする
delete logout_path
# ログアウトパスを削除
# cookieを削除してログイン
log_in_as(@user, remember_me: '0')
assert_empty cookies[:remember_token]
# 空どうか確認
# クライアント側に保存されているか?
# :remember_tokenでcookiesができるのか。
end
end
###test/models/user_test.rb
test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
# テストユーザー
# create_user.rbでusersテーブルに沿って作成
# 現時点では有効
end
test "should be valid" do
# 有効を検証
assert @user.valid?
# usersテーブルで有効なデータモデルか確認
end
test "name should be present" do
@user.name = ""
# 名前を空にする
assert_not @user.valid?
# 空にしたデータテーブルは無効か?
end
test "email should be present" do
# 存在性の検証
@user.email = " "
# メールを空にする
assert_not @user.valid?
# 無効か?
end
test "name should not be too long" do
# 長さの検証
@user.name = "a" * 51
# 文字列の長さを検証
assert_not @user.valid?
# 無効か?
end
test "email should not be too long" do
@user.email = "a" * 244 + "@example.com"
# 文字列の長さの検証
assert_not @user.valid?
# 無効か?
end
test "email validation should accept valid addresses" do
# メールフォーマットの検証をする
valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
first.last@foo.jp alice+bob@baz.cn]
# メールのアドレスのドメイン名の配列
valid_addresses.each do |valid_address|
# いくつものアドレスを代入していく。
@user.email = valid_address
assert @user.valid?, "#{valid_address.inspect} should be valid"
# ドメイン名は有効か?
# "#{valid_address.inspect} should be valid" エラ〜メッセージ
end
end
test "email validation should reject invalid addresses" do
invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
foo@bar_baz.com foo@bar+baz.com]
invalid_addresses.each do |invalid_address|
@user.email = invalid_address
assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
end
end
test "email addresses should be unique" do
# 一意性の検証
duplicate_user = @user.dup
# dup オブジェクトのコピーを作成するメソッド
# 同じものを作って比較する
duplicate_user.email = @user.email.upcase
# 大文字のアドレスに上書き
@user.save
assert_not duplicate_user.valid?
# 無効か?
end
test "password should be present (nonblank)" do
@user.password = @user.password_confirmation = " " * 6
# パスワードに空白を入力
assert_not @user.valid?
# 有効にしない
end
test "password should have a minimum length" do
@user.password = @user.password_confirmation = "a" * 5
# 五文字の文字列を入力
assert_not @user.valid?
# 有効にしない
end
test "authenticated? should return false for a user with nil digest" do
assert_not @user.authenticated?('')
# パスワードが空じゃないか?
end
end
###app/views/sessions/new.html.erb
app/views/sessions/new.html
.erb
<% provide(:title, "Log in") %>
<h1>ログイン</h1>
<!--セッションページ-->
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :remember_me, class: "checkbox inline" do %>
<!--パスワード記憶機能のチェックボックスを表示させる-->
<%= f.check_box :remember_me %>
<!--チェックボックス表示-->
<span>Remember me on this computer</span>
<!--<div>に似ているらしい-->
<% end %>
<%= f.submit "Log in", class: "btn btn-primary" %>
<!--ボタンを作る-->
<% end %>
<p>New user? <%= link_to "Sign up now!", signup_path %></p>
<!--文字のリンク-->
</div>
</div>
###app/assets/stylesheets/custom.scss
app/assets/stylesheets/custom.scss
@import "bootstrap-sprockets";
@import "bootstrap";
/* mixins, variables, etc. */
$gray-medium-light: #eaeaea;
@mixin box_sizing {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
// 箱のようなデザインにする
/* universal */
body {
padding-top: 60px;
}
section {
overflow: auto;
}
textarea {
resize: vertical;
}
.center {
text-align: center;
h1 {
margin-bottom: 10px;
}
}
/* typography */
h1, h2, h3, h4, h5, h6 {
line-height: 1;
}
h1 {
font-size: 3em;
letter-spacing: -2px;
margin-bottom: 30px;
text-align: center;
}
h2 {
font-size: 1.2em;
letter-spacing: -1px;
margin-bottom: 30px;
text-align: center;
font-weight: normal;
color: #777;
}
p {
font-size: 1.1em;
line-height: 1.7em;
}
/* header */
#logo {
float: left;
margin-right: 10px;
font-size: 1.7em;
color: #0f0;
text-transform: uppercase;
letter-spacing: -1px;
padding-top: 9px;
font-weight: bold;
&:hover {
color: #008000;
text-decoration: none;
}
}
/* footer */
footer {
margin-top: 45px;
padding-top: 5px;
border-top: 1px solid #eaeaea;
color: #777;
a {
color: #555;
&a:hover {
color: #222;
}
}
small {
float: left;
}
ul {
float: right;
list-style: none;
li {
float: left;
margin-left: 15px;
}
}
}
/* miscellaneous */
.debug_dump {
clear: both;
float: left;
width: 100%;
margin-top: 45px;
@include box_sizing;
}
/* forms */
input, textarea, select, .uneditable-input {
border: 1px solid #bbb;
width: 100%;
margin-bottom: 15px;
@include box_sizing;
}
input {
height: auto !important;
}
#error_explanation {
color: red;
ul {
color: red;
margin: 0 0 30px 0;
}
}
.field_with_errors {
@extend .has-error;
.form-control {
color: $state-danger-text;
}
}
.checkbox {
margin-top: -10px;
margin-bottom: 10px;
span {
margin-left: 20px;
font-weight: normal;
}
}
#session_remember_me {
width: auto;
margin-left: 0;
}
//これから勉強する
###test/test_helper.rb
test/test_helper.rb
class ActiveSupport::TestCase
fixtures :all
# テストユーザーがログイン中の場合にtrueを返す
def is_logged_in?
!session[:user_id].nil?
end
# テストユーザーとしてログインする
def log_in_as(user)
session[:user_id] = user.id
# セッションに情報を保存
# session[キー] = 値
end
end
class ActionDispatch::IntegrationTest
# テストユーザーとしてログインする
def log_in_as(user, password: 'password', remember_me: '1')
# テストユーザーとしてログイン 有効なユーザー、 パスワード、記憶させる
post login_path, params: { session: { email: user.email,
password: password,
remember_me: remember_me } }
# 投稿する
end
end
###test/helpers/sessions_helper_test.rb
test/helpers/sessions_helper_test.rb
require 'test_helper'
class SessionsHelperTest < ActionView::TestCase
def setup
# テストユーザーをデータベースに記憶させる
@user = users(:michael)
remember(@user)
end
test "current_user returns right user when session is nil" do
assert_equal @user, current_user
# データベース上のユーザーとログイン中のユーザーが同じか?
assert is_logged_in?
# ログイン中か?
end
test "current_user returns nil when remember digest is wrong" do
@user.update_attribute(:remember_digest, User.digest(User.new_token))
# 記憶ダイジェストに新しくダイジェストを上書きする
assert_nil current_user
# assert_nil(変数 [, メッセージ]) 変数がnilならば成功
# ログイン中ではないか?
end
end
###sessions_helper.rbをテスト
.
.
.
def current_user
if (user_id = session[:user_id])
# セッションに情報を保存
# session[キー] = 値
@current_user ||= User.find_by(id: user_id)
# idにuser_idをもとにデータベースから探す又はcurrent_userに代入する
elsif (user_id = cookies.signed[:user_id])
# 署名付きのクッキーを設定できたら
raise
# raise 発生させたい例外のクラス 以下
user = User.find_by(id: user_id)
# user_idを基に探す
if user && user.authenticated?(cookies[:remember_token])
# クッキーとは、クライアント側に保存されるファイル
# cookies[:クッキー名] = { key: クッキー情報 }
# userが有効且つクッキーが認証されているか?
log_in user
# ログイン
@current_user = user
# @current_userにする
end
end
end
.
.
.
ubuntu:~/environment/my_app (advanced-login) $ rails test test/helpers/sessions_helper_test.rb
Running via Spring preloader in process 8387
Run options: --seed 63395
# Running:
E
Error:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil:
RuntimeError:
app/helpers/sessions_helper.rb:20:in `current_user'
test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>'
rails test test/helpers/sessions_helper_test.rb:10
test_current_user_returns_right_user_when_session_is_nil
テスト現在のユーザーは、セッションがnilのときに正しいユーザーを返します
user = User.find_by(id: user_id)
が無効になっているからかな?
続き↓
E
Error:
SessionsHelperTest#test_current_user_returns_nil_when_remember_digest_is_wrong:
RuntimeError:
app/helpers/sessions_helper.rb:20:in `current_user'
test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>'
rails test test/helpers/sessions_helper_test.rb:15
test_current_user_returns_nil_when_remember_digest_is_wrong
記憶ダイジェストが違う時にログイン中のユーザーがnilと返される
続き↓
Finished in 0.086793s, 23.0433 runs/s, 0.0000 assertions/s.
2 runs, 0 assertions, 0 failures, 2 errors, 0 skips