35歳だけどRailsチュートリアルやってみた。[第4版 12章 12.2 パスワード再設定のメール送信 まとめ&解答例]

#はじめに
最近、プロジェクト管理業務が業務の大半を占めており、
プログラムを書く機会がなかなかありません。

このままだとプログラムがまったく書けない人になってしまう危機感（迫り来る35歳定年説）と、
新しいことに挑戦したいという思いから、
[Ruby on Rails チュートリアル実例を使ってRailsを学ぼう 第4版](https://railstutorial.jp/)を学習中です。
業務で使うのはもっぱらJavaなのですが、Rails楽しいですね。


これまでEvernoteに記録していましたが、ソースコードの貼付けに限界を感じたため、
Qiitaで自分が学習した結果をアウトプットしていきます。

個人の解答例なので、誤りがあればご指摘ください。

##動作環境
- cloud9
- ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
- Rails 5.0.0.1

##12.2.1 パスワード再設定のメールとテンプレート
###本章での学び
####【Mailer】Userメイラーの実装
自動生成されたメール送信処理のメソッド`password_reset`の中身を実装する。

```/sample_app/app/mailers/user_mailer.rb
  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.password_reset.subject
  #
  def password_reset
    @greeting = "Hi"

    mail to: "to@example.org"
  end
```

上記のコードは削除して、下記の通り編集する。

```/sample_app/app/mailers/user_mailer.rb
  def password_reset(user)
    @user = user
    mail to: user.email, subject: "Password reset"
  end
```

####【Mailer】メールテンプレートの実装
自動生成されているtextメールのテンプレートの中身を実装する。

```/sample_app/app/views/user_mailer/password_reset.text.erb
To reset your password click the link below:

<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

This link will expire in tow hours.

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

自動生成されているhtmlメールのテンプレートの中身を実装する。

```/sample_app/app/views/user_mailer/password_reset.html.erb
<h1>Password reset</h1>

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

<%= link_to "Reset password", edit_password_reset_url(@user.reset_token, email: @user.email) %>

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

####【Mailer】プレビュー処理の実装
自動生成されたプレビュー処理の中身を実装する。

```/sample_app/test/mailers/previews/user_mailer_preview.rb
  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end
```

上記のコードを削除して、下記のとおり実装する。

```/sample_app/test/mailers/previews/user_mailer_preview.rb
  def password_reset
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
  end
```

####動作確認前の注意点
本項目の実行環境は、developであるため、実際にメールは送信されない。
（production環境でなければ、実際にメールは飛ばない）
私はdevelop環境であることを失念していたので、メールが飛ばないのはなぜかと、小一時間悩みました。。。

####プレビュー処理の動作確認
下記のURLにアクセスして動作確認を行う。
https://xxxxxxx/rails/mailers/user_mailer/password_reset

textメールのプレビューができることを確認。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/c9cae164-1184-cb3d-045d-4bfe807edf11.png)

htmlメールのプレビューができることを確認。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/23db2a95-dfd6-a937-560b-03cc2f455852.png)


####パスワード再設定メール送信処理の動作確認
ブラウザを起動し、有効なメールアドレスを入力して、パスワード再設定メールを送信する。
動作確認のためのメールアドレスは、[10分メールアドレス](https://10minutemail.net/?lang=ja)を使うと便利。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/6921bed7-c8a6-3c8a-a3bb-f1b6710fec0c.png)

ブラウザ上で、パスワード再送信メールが送信できたメッセージが表示されたことを確認。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/89f5855f-72c3-cf93-3717-3f27e8a36237.png)


####コンソールログの確認
入力したメールアドレス宛に、パスワード再設定メールが送信されていることを確認。

```irb
Sent mail to hsa09793@tqosi.com (8.3ms)
Date: Sun, 09 Jul 2017 01:24:46 +0000
From: noreply@example.com
To: hsa09793@tqosi.com
Message-ID: <596185de75f24_dcf20b491898558@yokoyan-rails-tutorial-4550834.mail>
Subject: Password reset
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_596185de74823_dcf20b491898458";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


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

To reset your password click the link below:

https://rails-tutorial-yokoyan.c9users.io/password_resets/UDO5Wja1Uh_eiltpVaPxpw/edit?email=hsa09793%40tqosi.com

This link will expire in tow hours.

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


----==_mimepart_596185de74823_dcf20b491898458
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="https://rails-tutorial-yokoyan.c9users.io/password_resets/UDO5Wja1Uh_eiltpVaPxpw/edit?email=hsa09793%40tqosi.com">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>
```

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

UTC時間が表示される。
日本時間から9時間ずれて表示される。

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

前述の「コンソールログの確認」参照。

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

演習２で使用したユーザーのメールアドレスで検索。
`reset_digest`と、`reset_sent_at`に値が入っていることを確認。

```irb
yokoyan:~/workspace/sample_app (password-reset) $ rails console --sandbox
Running via Spring preloader in process 3587
Loading development environment in sandbox (Rails 5.0.0.1)
Any modifications you make will be rolled back on exit
>> 
?> user = User.find_by(email: "hsa09793@tqosi.com")
  User Load (0.4ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "hsa09793@tqosi.com"], ["LIMIT", 1]]
=> #<User id: 103, name: "mail_test", email: "hsa09793@tqosi.com", created_at: "2017-07-09 01:06:40", updated_at: "2017-07-09 01:24:46", password_digest: "$2a$10$F42JIypvQYXBt6V2pe7A4ONXQ4PImXqR0GSK7ioCrj5...", remember_digest: nil, admin: false, activation_digest: "$2a$10$V4Z1Rgva0TA.FJqv7/SWAO/vfjX0sBSpgLpKjx8itK7...", activated: nil, activated_at: nil, reset_digest: "$2a$10$dsfjyWCpuX.9I4c7zbWYieBp4nxc.fzipFaYHyaHcP8...", reset_sent_at: "2017-07-09 01:24:46">
>> 
?> user.reset_digest
=> "$2a$10$dsfjyWCpuX.9I4c7zbWYieBp4nxc.fzipFaYHyaHcP83yMQRBtFaO"
>> 
?> user.reset_sent_at
=> Sun, 09 Jul 2017 01:24:46 UTC +00:00
```

##12.2.2 送信メールのテスト
###本章での学び
####【test】パスワード再設定用メイラーメソッドのテストを作成
11章でコメントアウトしていたテストコードを実装する。

- michaelユーザの情報を取得
- パスワード再設定トークンを生成
- パスワード再設定メールを送信
- メールオブジェクトの件名に、"Password reset"が含まれているかチェック
- メールオブジェクトの宛先が、ユーザのemailと一致しているかチェック
- メールオブジェクトのfromアドレスが、`noreply@example.com`と一致しているかチェック
- テストユーザのパスワード再設定トークンが、エンコードされたメール本文内に存在するかチェック
- テストユーザのアドレスがエスケープされて、エンコードされたメール本文に含まれているかチェックする

上記を踏まえて実装する。

```/sample_app/test/mailers/user_mailer_test.rb
  test "password_reset" do
    user = users(:michael)
    user.reset_token = User.new_token
    mail = UserMailer.password_reset(user)
    assert_equal "Password reset", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.reset_token, mail.body.encoded
    assert_match CGI.escape(user.email), mail.body.encoded
  end
```

####動作確認
テストコードがgreenになることを確認。

```irb
yokoyan:~/workspace/sample_app (password-reset) $ rails test
Running via Spring preloader in process 5896
Started with run options --seed 1346

  47/47: [===================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.88912s
47 tests, 205 assertions, 0 failures, 0 errors, 0 skips
```

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

メイラーのテストだけ実行する。
`rails test test/ディレクトリ名/ファイル名`
greenになることを確認。

```irb
yokoyan:~/workspace/sample_app (password-reset) $ rails test test/mailers/user_mailer_test.rb
Running via Spring preloader in process 6095
Started with run options --seed 2562

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

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

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

エスケープ処理をコメントアウトする。

```/sample_app/test/mailers/user_mailer_test.rb
#    assert_match CGI.escape(user.email), mail.body.encoded
    assert_match user.email, mail.body.encoded
```

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

```irb
yokoyan:~/workspace/sample_app (password-reset) $ rails test test/mailers/user_mailer_test.rb
Running via Spring preloader in process 6187
Started with run options --seed 5175

 FAIL["test_password_reset", UserMailerTest, 0.6262194160372019]
 test_password_reset#UserMailerTest (0.63s)
        Expected /michael@example\.com/ to match # encoding: US-ASCII
        "\r\n----==_mimepart_596197d92db13_182bcc70e015864\r\nContent-Type: text/plain;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nTo reset your password click the link below:\r\n\r\nhttp://example.com/password_resets/IWAHQV67Gyu6BAQUaz9OoA/edit?email=michael%40example.com\r\n\r\nThis link will expire in tow hours.\r\n\r\nIf you did not request your password to be reset, please ignore this email and your password will stay as it is.\r\n\r\n\r\n----==_mimepart_596197d92db13_182bcc70e015864\r\nContent-Type: text/html;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n  <head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <style>\r\n      /* Email styles need to be inline */\r\n    </style>\r\n  </head>\r\n\r\n  <body>\r\n    <h1>Password reset</h1>\r\n\r\n<p>To reset your password click the link below:</p>\r\n\r\n<a href=\"http://example.com/password_resets/IWAHQV67Gyu6BAQUaz9OoA/edit?email=michael%40example.com\">Reset password</a>\r\n\r\n<p>This link will expire in two hours.</p>\r\n\r\n<p>\r\n  If you did not request your password to be reset, please ignore this email and your password will stay as it is.\r\n</p>\r\n\r\n  </body>\r\n</html>\r\n\r\n----==_mimepart_596197d92db13_182bcc70e015864--\r\n".
        test/mailers/user_mailer_test.rb:25:in `block in <class:UserMailerTest>'

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

Finished in 0.63150s
2 tests, 16 assertions, 1 failures, 0 errors, 0 skips
```

テスト実行後は元に戻しておく。

#おわりに
パスワード再設定メールの総身諸利を実装することができました。
まだ、production環境にデプロイしていないため、実際にパスワード再設定メールが飛ぶかどうかの確認ができていません。（ここは、12.4 本番環境でのメール送信 (再掲)で実施）
今回、develop環境でメールが飛ばないことを失念しており、はまってしまったのでみなさんもご注意ください。。。