(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第12章】

##前提
・Railsチュートリアルは第4版
・今回の学習は3周目（9章以降は2周目）
・著者はProgate一通りやったぐらいの初学者
##基本方針
・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる（記事最下段・用語集）。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

　
　認証システム開発・第6段回目、最終回の第12章です！世間は4連休ですが、学習に休みはありません。ばりばりやっていきましょう。不断の努力を。
　
本日の一曲はこちら。
[SUPERCAR "PLANET short ver."](https://www.youtube.com/watch?v=DzKi3BMCMAc)
良い曲は何年経っても色褪せません。

　
####【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など）が使えるけど、今回はそういった変数がない。


　
####【12.1.3　createアクションでパスワード再設定　演習】
1.　試しに有効なメールアドレスをフォームから送信してみましょう (図 12.6)。どんなエラーメッセージが表示されたでしょうか?
→　ArgumentError in PasswordResetsController#create
wrong number of arguments (given 1, expected 0)

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

```
>> user = User.find_by(email: "kawa@kawa.com")
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "kawa@kawa.com"], ["LIMIT", 1]]
=> #<User id: 102, name: "kawa", email: "kawa@kawa.com", created_at: "2020-09-17 22:39:47",
 updated_at: "2020-09-20 04:23:52", password_digest: "$2a$10$kgv1Loz8fVDaaZvtUMtkZOUBnbCcHZNIBQBrgb18QMj...", remember_digest: nil, admin: false, activation_digest: "$2a$10$bmgQ2XztK7kgePhH8pVDiuKenXFDEl51XktqmfPUwHv...", activated: true, activated_at: "2020-09-17 22:40:33", 
reset_digest: "$2a$10$iuW.1GDheym2P5Nkuo7QUu7YjCs1DyooYonE0RY2lck...", reset_sent_at: "2020-09-20 04:23:52">
```


　
####【12.2.1　パスワード再設定のメールとテンプレート　演習】
1.　ブラウザから、送信メールのプレビューをしてみましょう。「Date」の欄にはどんな情報が表示されているでしょうか?
→　Date: Sun, 20 Sep 2020 04:49:13 +0000

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

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

To reset your password click the link below:

https://545f54b8b0d74dfd8bcc26b33cb0f3fe.vfs.cloud9.us-east-2.amazonaws.com/password_resets/TaQ06fMRyqZk-wHJp8fnyw/edit?email=kawa%40kawa.com

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.
```

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

```
reset_digest: "$2a$10$JUgxhUTG.XKFk7BnZqfLHeU8fdUIU/cnMvBGaAs.RCX...", 
reset_sent_at: "2020-09-20 04:48:06">
```


　
####【12.2.2　送信メールのテスト　演習】
1.　メイラーのテストだけを実行してみてください。このテストは greenになっているでしょうか?
→　$ rails test test/mailers/user_mailer_test.rb　でGREEN

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


　
####【12.3.1　editアクションで再設定　演習】
1.　12.2.1.1で示した手順に従って、Railsサーバーのログから送信メールを探し出し、そこに記されているリンクを見つけてください。そのリンクをブラウザから表示してみて、図 12.11のように表示されるか確かめてみましょう。
→　Reset password画面が表示されました。

　
2.　先ほど表示したページから、実際に新しいパスワードを送信してみましょう。どのような結果になるでしょうか?
→　Unknown action
The action 'update' could not be found for PasswordResetsController


　
####【12.3.2　パスワードを更新する　演習】
1.　12.2.1.1で得られたリンク (Railsサーバーのログから取得) をブラウザで表示し、passwordとconfirmationの文字列をわざと間違えて送信してみましょう。どんなエラーメッセージが表示されるでしょうか?
→　Password confirmation doesn't match Password

　
2.　コンソールに移り、パスワード再設定を送信したユーザーオブジェクトを見つけてください。見つかったら、そのオブジェクトのpassword_digestの値を取得してみましょう。次に、パスワード再設定フォームから有効なパスワードを入力し、送信してみましょう (図 12.13)。パスワードの再設定は成功したら、再度password_digestの値を取得し、先ほど取得した値と異なっていることを確認してみましょう。ヒント: 新しい値はuser.reloadを通して取得する必要があります。
→　およ、パスワードを再設定しようとしたら「SQLite3::BusyException: database is locked: commit transaction」が。[こちらの記事を参考に](https://qiita.com/opuoth/items/8bfc33437f70b9108b23)、RailsコンソールでActiveRecord::Base.connection.execute("BEGIN TRANSACTION; END;")　を打ったら動作しました。結果下記。異なってますね。
　変更前：\$2a$10$kgv1Loz8fVDaaZvtUMtkZOUBnbCcHZNIBQBrgb18QMjyvnK.U3vlW
　変更後：\$2a$10$/lAIMLbkR84Zg0rBkVmcIeWn6u/UBEOaHGrU34rLR8ZMnmcIEomiu

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

```ruby: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
```

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

```ruby: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/i, response.body
  end
```

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

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

```ruby:password_resets_test.rb
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
        params: { email: user.email,
            user: { password:        "foobaz",
              password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_nil user.reload.reset_digest
    assert_not flash.empty?
    assert_redirected_to user
```


　
####【12.4　本番環境でのメール送信　演習】
　ここはカット！！理由は前章同様！！


　
###第12章まとめ
・前章同様にリソースでモデル化。
・再設定リンクに有効期限を設けるため、Userモデルにreset_sent_atも追加（これを基準時にするため）
・hidden_field_tagで、一度送信後（editアクション）もメールアドレスを保持。
・@user.errors.add(:password, :blank)で空文字列を弾く。

　
　ついに認証システム開発が終わりました。ここまでやってるといやでもトークンとかダイジェストとかの用語が身に染み付いてきますね。ちょっと分からなくても、とにかく食べて食べまくってたら消化して血肉になっていきます。がんばりましょう。
　次章は投稿機能を実装していきます！


　
⇨　[第13章へ！](https://qiita.com/t-kawamura1/items/9fc7596636102360d759)
⇦　[第11章はこちら](https://qiita.com/t-kawamura1/items/030d7239837230cfcc9a)
[学習にあたっての前提・著者ステータスはこちら](https://qiita.com/t-kawamura1/items/9c955177c02871b5249c)
　
####なんとなくイメージを掴む用語集
今回はありません。