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

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


第12章 パスワードの再設定 - Railsチュートリアル

f:id:yukitoku_sw:20191019165508p:plain


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



演習12.1.1 PasswordResetsコントローラ

問題1

この時点で、テストスイートが greenになっていることを確認してみましょう。

解答

テストを実行し、greenであることを確認


問題2

表 12.1の名前付きルートでは、pathではなくurlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。

解答

外部からアクセスするため完全なurlが必要


12.1.2 新しいパスワードの設定

問題1

リスト 12.4のform_forメソッドでは、なぜ@password_resetではなく:password_resetを使っているのでしょうか? 考えてみてください。

解答

password_resetはUserモデルに変更を加えるコントローラのためインスタンス変数を使う場合@userになる。しかし、form_forに@userを使うと/usersに対応するPOSTであると認識されるのでsignupに対応してしまう。なので、シンボル:password_resetを使用している。


演習12.1.3 createアクションでパスワード再設定

問題1

試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?

解答

有効なメールアドレスを送信するとエラーメッセージが表示される

f:id:yukitoku_sw:20191102201551p:plain


問題2

コンソールに移り、先ほどの演習課題で送信した結果、(エラーと表示されてはいるものの) 該当するuserオブジェクトにはreset_digestとreset_sent_atがあることを確認してみましょう。また、それぞれの値はどのようになっていますか?

解答

ブラウザではエラーとなっているがコンソールで確認するとそれぞれ値が入力されている

>> user = User.find_by(email: "example@railstutorial.org")
(省略)
>> user.reset_digest
=> "$2a$10$UoAfAt0uzz4pWWUSFMLfw.Oenwz7ICF2cjJSV1HGMsA9kxkSUHJ2u"
>> user.reset_sent_at
=> Sat, 02 Nov 2019 11:15:13 UTC +00:00


演習12.2.1 パスワード再設定のメールとテンプレート

問題1

ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか?

解答

Dateの欄にはUTC時間が表示される。日本時間ではない。

f:id:yukitoku_sw:20191102202715p:plain


問題2

パスワード再設定フォームから有効なメールアドレスを送信してみましょう。また、Railsサーバーのログを見て、生成された送信メールの内容を確認してみてください。

解答

UserMailer#password_reset: processed outbound mail in 36.0ms
Sent mail to example@railstutorial.org (3.5ms)
Date: Sat, 02 Nov 2019 20:27:52 +0900
From: noreply@example.com
To: example@railstutorial.org
Message-ID: <5dbd6838add8a_5df13ffdb70548cc9850@mba.mail>
Subject: Password reset
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_5dbd6838ad230_5df13ffdb70548cc98410";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_5dbd6838ad230_5df13ffdb70548cc98410
Content-Type: text/plain;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

To reset your password click the link below:

http://localhost:3000/password_resets/O5OMXRb6tflHxa8ByImYeg/edit?email=example%40railstutorial.org

This link will expire in two hours.

If you did not request your password to be reset, please ignore this email and
your password will stay as it is.

----==_mimepart_5dbd6838ad230_5df13ffdb70548cc98410
Content-Type: text/html;
 charset=UTF-8
Content-Transfer-Encoding: 7bit

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <style>
      /* Email styles need to be inline */
    </style>
  </head>

  <body>
    <h1>Password reset</h1>

<p>To reset your password click the link below:</p>

<a href="http://localhost:3000/password_resets/O5OMXRb6tflHxa8ByImYeg/edit?email=example%40railstutorial.org">Reset password</a>

<p>This link will expire in two hours.</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
  </body>
</html>

----==_mimepart_5dbd6838ad230_5df13ffdb70548cc98410--

Redirected to http://localhost:3000/
Completed 302 Found in 153ms (ActiveRecord: 7.1ms)


問題3

コンソールに移り、先ほどの演習課題でパスワード再設定をしたUserオブジェクトを探してください。オブジェクトを見つけたら、そのオブジェクトが持つreset_digestとreset_sent_atの値を確認してみましょう。

解答

>> user = User.first
(省略)
>> user.reset_digest
=> "$2a$10$fqmrs.nZER5KznyyjYht4uI3otro3Zqxklj6EMlegSWKXlLCnN2.G"
>> user.reset_sent_at
=> Sat, 02 Nov 2019 11:27:52 UTC +00:00


演習12.2.2 送信メールのテスト

問題1

メイラーのテストだけを実行してみてください。このテストは greenになっているでしょうか?

解答

$ rails test:mailers
Started with run options --seed 58552

  2/2: [===============================================================] 100% Time: 00:00:00, Time: 00:00:00

Finished in 0.39337s
2 tests, 16 assertions, 0 failures, 0 errors, 0 skips


問題2

リスト 12.12にある2つ目のCGI.escapeを削除すると、テストが redになることを確認してみましょう。

解答

CGI.escapeを削除するとテストred になる。


演習12.3.1 editアクションで再設定

問題1

12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。

解答

f:id:yukitoku_sw:20191102210048p:plain


問題2

先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?

解答

f:id:yukitoku_sw:20191102210213p:plain


演習12.3.2 パスワードを更新する

問題1

12.2.1.1で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?

解答

f:id:yukitoku_sw:20191102213036p:plain


問題2

コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう (図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。

解答

パスワードの再設定を行うと、password_digestが更新されることを確認

>> user = User.first
  (省略)
>> user.password_digest
=> "$2a$10$7jaVrE3ZM2vaJMeS.Rjr2ufZqH7WtJ4QV49FIxV5KkiSwOPZwKvl2"

# パスワードの再設定を行う

>> user.reload
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIM
IT", 1]]
(省略)
>> user.password_digest
=> "$2a$10$ZzyZENoEMcOQx6UOj325Yez2q6GcU9WsBAaqexlZByDTYSJQCFan."


演習12.3.3 パスワードの再設定をテストする

問題1

リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。ちなみにリスト 12.20にあるコードには、前章の演習 (リスト 11.39) の解答も含まれています。

解答

# パスワード再設定の属性を設定する
def create_reset_digest
  self.reset_token = User.new_token
  update_columns(reset_digest: User.digest(reset_token),
                reset_sent_at: Time.zone.now)
end

テストでgreenになることを確認


問題2

リスト 12.21のテンプレートを埋めて、期限切れのパスワード再設定で発生する分岐 (リスト 12.16) を統合テストで網羅してみましょう (12.21 のコードにあるresponse.bodyは、そのページのHTML本文をすべて返すメソッドです)。期限切れをテストする方法はいくつかありますが、リスト 12.21でオススメした手法を使えば、レスポンスの本文に「expired」という語があるかどうかでチェックできます (なお、大文字と小文字は区別されません)。

解答

[test/integration/password_resets_test.rb]

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest
  
 .
 .
 .
  
  test "expired token" do
    get new_password_reset_path
    post password_resets_path,
        params: { password_reset: {email: @user.email } }
    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)
    patch password_reset_path(@user.reset_token),
        params: { email: @user.email,
            user: { password:  "foobar",
              password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match /expired/i, response.body
  end
end


問題3

2時間経ったらパスワードを再設定できなくする方針は、セキュリティ的に好ましいやり方でしょう。しかし、もっと良くする方法はまだあります。例えば、公共の (または共有された) コンピューターでパスワード再設定が行われた場合を考えてみてください。仮にログアウトして離席したとしても、2時間以内であれば、そのコンピューターの履歴からパスワード再設定フォームを表示させ、パスワードを更新してしまうことができてしまいます (しかもそのままログイン機構まで突破されてしまいます!)。この問題を解決するために、リスト 12.22のコードを追加し、パスワードの再設定に成功したらダイジェストをnilになるように変更してみましょう5。

解答

class PasswordResetsController < ApplicationController
 .
 .
 .

  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update_attributes(user_params)
      log_in @user
      @user.update_attribute(:reset_digest, nil)
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end
 .
 .
 .
end


問題4

リスト 12.18に1行追加し、1つ前の演習課題に対するテストを書いてみましょう。ヒント: リスト 9.25のassert_nilメソッドとリスト 11.33のuser.reloadメソッドを組み合わせて、reset_digest属性を直接テストしてみましょう。

解答

パスワードの再設定に成功したらダイジェストがnilになるテストを書く

test内の有効なパスワードとパスワード確認の中に追加する
assert_nilを使いuser.reloadをするとreset_digestがnilになることを検証する

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest
  
  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

  test "password resets" do
    (中略)
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
        params: { email: user.email,
            user: { password:  "foobaz",
              password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
    assert_nil user.reload.reset_digest   # 追加
  end
 .
 .
 .
end

テストがgreenになることを確認

演習12.4 本番環境でのメール送信(再掲)

問題1

production環境でユーザー登録を試してみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?

解答

省略


問題2

メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。

解答

省略

問題3

アカウントを有効化できたら、今度はパスワードの再設定を試してみましょう。正しくパスワードの再設定ができたでしょうか?

解答

省略


おわり


yukitoku-sw.hatenablog.com