第9章Railsチュートリアル演習問題と解答まとめ

Ruby on Railsチュートリアルの演習問題と解答をまとめる。


第9章 発展的なログイン機構 - Railsチュートリアル

f:id:yukitoku_sw:20191019165508p:plain

アウトプットすることで、より自分の理解を深めることを目的としています。 自分なりに調べて考えた回答のため、記載内容に誤りがある場合はコメントいただけると幸いです。


演習9.1.1 記憶トークンと暗号化

問題1

コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。

解答

>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2019-10-22 11:28:15", updated_at: "2019-10-22 11:28:15", password_digest:"$2a$10$qJNgcZhLTc4MMK1hAcmuk.QnAXOdwbkcu65k9FCE4Zh...", remember_digest: nil>
>> user.remember
   (0.1ms)  begin transaction
  SQL (1.4ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2019-10-24 06:17:58.570963"], ["remember_digest", "$2a$10$Jn19ceDBERSUki1AU/wHle0ZS0yPw5di83QHuTlyIWOjEe9V6BKRm"], ["id", 1]]
   (2.2ms)  commit transaction
=> true

>> user.remember_token
=> "WB3ckrAFSrNFVM8VHJCTjQ"

>> user.remember_digest
=> "$2a$10$Jn19ceDBERSUki1AU/wHle0ZS0yPw5di83QHuTlyIWOjEe9V6BKRm"

remember_tokenは、base64によって長さ22の文字列
remember_digestは、BCryptによって長さ60の文字列


問題2

リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、ややわかりにくいリスト 9.4の実装でも、非常に混乱しやすいリスト 9.5の実装でも、いずれも正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。

解答

リスト9.4 もリスト9.5 もテストはGREEN


演習9.1.2 ログイン状態の保持

問題1

ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。

解答

f:id:yukitoku_sw:20191024160721p:plain

remember_tokenとuser_idが追加されている


問題2

コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。

解答

>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2019-10-22 11:28:15", updated_at: "2019-10-24 07:06:00", password_digest:"$2a$10$qJNgcZhLTc4MMK1hAcmuk.QnAXOdwbkcu65k9FCE4Zh...", remember_digest: "$2a$10$8VDBZindjJR3ThlPJtVRoOcaLzdDQbnP9z0TAe4atiE...">

>> user.remember
   (0.1ms)  begin transaction
  SQL (0.8ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2019-10-24 07:13:08.437799"], ["remember_digest","$2a$10$TFQh12eQXaaED.QNr6E0UOcTqA/dp/XYtBxOokoddKeXzxMhNCJ1y"], ["id", 1]]
   (1.4ms)  commit transaction
=> true

>> user.authenticated?(user.remember_token)
=> true

authenticated?メソッドの引数である(remember_token)を、rememberメソッドで作成する。
self.remember_tokenが作成されるので、引数には(user.remember_token)と入れる


演習9.1.3 ユーザーを忘れる

問題1

ログアウトした後に、ブラウザの対応するcookiesが削除されていることを確認してみましょう。

解答

デベロッパーツールで確認するとcookiesが削除されていることが確認できます。


演習9.1.4 2つ目の目立たないバグ

問題1

リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。

解答

動作確認のみのため省略


問題2

リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。

解答

動作確認のみなので省略


問題3

上のコードでコメントアウトした部分を元に戻し、テストスイートが red から greenになることを確認しましょう。

解答

コメントアウトを戻しましょーう


演習9.2 [Remember me] チェックボックス

問題1

ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。

解答

Remember meにチェックを付けてログインする。

Cookiesを調べる。

f:id:yukitoku_sw:20191024174745p:plain

一度ブラウザを閉じて、Homeページ(localhost:3000/)を開く。

ログイン状態のヘッダーが表示されているのを確認

Cookiesを調べると

f:id:yukitoku_sw:20191024175107p:plain

_sample_app_sessionは変わっているが、remember_tokenとuser_idは変わっていないので[Remember me]がちゃんと機能している。


問題2

コンソールを開き、三項演算子を使った実例を考えてみてください (コラム 9.2)。

解答

>> name = "kuro"
=> "kuro"
>> puts name == "kuro" ? "こんにちわ" : "だれ?"
こんにちわ
=> nil

>> name = "tama"
=> "tama"
>> puts name == "kuro" ? "こんにちわ" : "だれ?"
だれ?
=> nil

使い方はあっているはず!!!!


演習9.3.1 [Remember me] ボックスをテストする

問題1

リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。

解答

userを@userに変更。(インスタンス変数にするため)

[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])
      log_in @user
      params[:session][:remember_me] == '1' ? remember(@user) : forget(@user)
      redirect_to @user
    else
      flash.now[:danger] = 'Invalid email/password combination' 
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_url
  end
end
[test/integration/users_login_test.rb] 

require 'test_helper'

class UsersLoginTest < ActionDispatch::IntegrationTest
(中略)
  test "login with remembering" do
    log_in_as(@user, remember_me: '1')
    assert_equal cookies['remember_token'], assigns(:user).remember_token
  end

  test "login without remembering" do
    # クッキーを保存してログイン 
    log_in_as(@user, remember_me: '1')
    delete logout_path
    # クッキーを削除してログイン
    log_in_as(@user, remember_me: '0')
    assert_empty cookies['remember_token']
  end
end

assert_equal(期待される値, 実際の値) assigns(:user).remember_tokenの値は、cookies['remember_token']と同じのはず!!というテスト


演習9.3.2 [Remember me] をテストする

問題1

リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。

解答

動作確認のみです



yukitoku-sw.hatenablog.com