読者です 読者をやめる 読者になる 読者になる

新米パパの育児留学

子供との幸せな時間を今だけではなく、一生大切にしたいという思いから、育休中にプログラミングを学習しエンジニアを目指します。

【第12章】Ruby on Rails チュートリアル 5.0(第4版)演習と解答まとめ

プログラミング プログラミング-Rails_チュートリアル

Ruby on Rails Tutorial最新版の演習と解答です。

f:id:mochikichi321:20170206212242p:plain

目的

Ruby on Rails チュートリアル 5.0(第4版)を学習中です。

学習を進める中で演習問題の解答がなかった(*1,2)ので、自分なりにまとめていくこととしました。

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

(*1)著者による有償版の解答はあるようです。正式な解答をご希望の方はこちらを参照ください。

Learn Web Development with Rails: Michael Hartl's Ruby on Rails Tutorial | Softcover.io

(*2)旧版に関する解答はありましたが、最新版 5.0(第4版)に関しては検索しても出てきませんでした。

プログラミングを学習し始めたきっかけについてはこちら

mochikichi.hatenablog.com

演習問題と解答

演習12.1.1

演習12.1.1.1

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

<解答> 動作確認のみなので省略。

演習12.1.1.2

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

<解答> 演習 (11.1.1.1) と同じ。

演習12.1.2

演習12.1.2.1

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

<解答> シンボルを使うとよりシンプルなformタグが生成され、オブジェクトを渡すとそのオブジェクトに寄って良しなに出し分けてくれる。newやeditのviewを準備する時に、同じ_form部分テンプレートを利用した時、同じ書き方で出し分けてくれる。 (参考:http://ja.stackoverflow.com/questions/18099/rails%E3%81%AEform-for%E3%81%AB%E3%82%B7%E3%83%B3%E3%83%9C%E3%83%AB%E3%82%92%E4%B8%8E%E3%81%88%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AF%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AA%E3%81%A8%E3%81%8D%E3%81%8B

演習12.1.3

演習12.1.3.1

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

<解答> f:id:mochikichi321:20170309084455p:plain

演習12.1.3.2

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

<解答> reset_digestとreset_sent_at共に値は"nil"となっている。

演習12.2.1

演習12.2.1.1

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

<解答> Dateはブラウザでメールを表示した時点の日時。

演習12.2.1.2

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

<解答> 動作確認のみなので省略。

演習12.2.1.3

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

<解答>

reset_digest: "$2a$10$ymOyGT8NRw5KEL/EvODcMupxE8/YKvSklgHMT0ZT3a1...", 
reset_sent_at: "2017-02-22 13:33:50"

演習12.2.2

演習12.2.2.1

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

<解答> GREEN

演習12.2.2.2

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

<解答> 動作確認のみなので省略。

演習12.3.1

演習12.3.1.1

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

<解答> 動作確認のみなので省略。

演習12.3.1.2

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

<解答> f:id:mochikichi321:20170309084532p:plain

演習12.3.2

演習12.3.2.1

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

<解答> The form contains 1 error. Password confirmation doesn’t match Password

演習12.3.2.2

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

<解答> 動作確認のみなので省略。

演習12.3.3

演習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) の解答も含まれています。

<解答>

[user.rb]

(前略)
  # パスワード再設定の属性を設定する
  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

演習12.3.3.2

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

<解答>

[password_resets_test.rb]

(前略)
  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", response.body   <---注目
  end

演習12.3.3.3

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

<解答> 動作確認のみなので省略。

演習12.3.3.4

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

<解答>

[password_resets_test.rb]

require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

(中略)

  test "password resets" do

(中略)

    assert_redirected_to user
    assert_nil user.reload['reset_digest']   <---注目
  end

演習12.4

演習12.4.1

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

<解答> 動作確認のみなので省略。

演習12.4.2

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

<解答> 動作確認のみなので省略。

演習12.4.3

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

<解答> 動作確認のみなので省略。

関連記事

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com

mochikichi.hatenablog.com