35歳だけどRailsチュートリアルやってみた。[第4版 11章 11.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

##11.2.1 送信メールのテンプレート
###本章での学び
アカウント有効化メールを送信するために、Action Mailerライブラリを使用して、メイラーを作成する。

####【mailer】Userメイラーの作成
`rails generate`で自動生成する。
viewのテンプレートが2つ生成される。

- テキストメール用
- HTMLメール用

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails generate mailer UserMailer account_activation password_reset
Running via Spring preloader in process 1731
Expected string default value for '--jbuilder'; got true (boolean)
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/account_activation.text.erb
      create    app/views/user_mailer/account_activation.html.erb
      create    app/views/user_mailer/password_reset.text.erb
      create    app/views/user_mailer/password_reset.html.erb
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb
```

####【mailer】メールテンプレートのカスタマイズ
デフォルトのfromアドレスを編集する。
なお、この値はアプリケーション全体で共通となる。

```/sample_app/app/mailers/application_mailer.rb
  default from: 'noreply@example.com'
```

####【mailer】メール送信処理の追加
自動生成された、account_activationメソッドを編集する。
ユーザー情報のインスタンス変数を作成し、`user.email`宛にメールを送信する。

```/sample_app/app/mailers/user_mailer.rb
  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end
```

####【view】テキストメールとHTMLメールビューのカスタマイズ
ユーザーの有効化URLには、有効化トークンを含める。
`q5lt38hQDc_959PVoo6b7A`の文字列は、実装済みの`new_token`メソッドで生成した値。
Base64でエンコードされている。

なお、AccountActivationsコントローラのeditアクションでは、paramsハッシュで`params[:id]`として参照することができる。

```ruby
http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit
```

また、URLからユーザーを特定するために、メールアドレスをクエリパラメータで追加する。
URLの末尾に？を付与して、キーと値のペアを記載する。

なお、URLに記載するメールアドレスの@は、URLで使えない文字列であるため
`%40`でエスケープする必要がある。
Railsでは自動的にエスケープしてくれる。

```ruby
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
```

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

テキストメールのテンプレート。

```/sample_app/app/views/user_mailer/account_activation.text.erb
Hi <%= @user.name %>

Welcome to the Sample App! Click on the link below to active your account:

<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
```

HTMLメールのテンプレート。

```/sample_app/app/views/user_mailer/account_activation.html.erb
<h1>Sample App</h1>

<p>Hi <%= @user.name %></p>,

<p>
  Welcome to the Sample App! Click on the link below to activate your account:
</p>

<%= link_to "Activate", edit_account_activation_url(@user.activation_token, email: @user.email) %>
```


####テストコードの作成
###演習1
*コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don’t panic!"をエスケープすると、どんな結果になりますか?*

実行結果は以下の通り。
@や、'、!がエスケープされて表示される。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 1755
Loading development environment (Rails 5.0.0.1)
>> CGI.escape('foo@example.com')
=> "foo%40example.com"
>> 
>> CGI.escape("Don't panic!")                                                                                                                                                                                                   
=> "Don%27t+panic%21"
>> 
```

##11.2.2 送信メールのプレビュー
###本章での学び
特殊なURLにアクセスして、メールの内容をその場でプレビューする。

####【environment】develop環境のメール設定変更
ホスト名を各自の環境に合わせて設定する。

```/sample_app/config/environments/development.rb
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'xxxxxxxxxxxx.c9users.io'
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
```

設定が完了したら、develop環境のサーバを再起動する。

####【mailer】Userメイラーのプレビューファイルの修正
自動生成されたプレビューファイルは、そのままでは動かないため、ユーザー情報を追加する。

- DBの最初のユーザー
- 有効化トークン（メールテンプレート内で使用しているため省略不可）

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

####動作確認
ブラウザから、下記URLにアクセスする。

https://xxxxxxxxx.c9users.io/rails/mailers/user_mailer/account_activation.html

メイラーのプレビュー画面が表示される。

![image](https://qiita-image-store.s3.amazonaws.com/0/98130/e09c5934-9548-ca21-3b34-31a98ae81993.png)


###演習1
*Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?*

上記の動作確認の通り。
「Date」には、UTC時間が表示されているため、日本と9時間ずれてる。


##11.2.3 送信メールのテスト
###本章での学び
前項で実装した、メールプレビューのテストコードを作成する。

####【test】Userメイラーのテストコードの作成

- テストユーザーとしてfixtureからmichaelを取得する
- テストユーザーの有効化トークンを生成する
- Userメイラーのアカウント有効化処理にテストユーザー情報を渡して、メールオブジェクトを作成する
- メールオブジェクトの件名をチェックする
- メールオブジェクトの送信先メールアドレスをチェックする
- メールオブジェクトの送信元メールアドレスをチェックする
- テストユーザーの名前が、メールの本文に含まれているかチェックする
- テストユーザーの有効化トークンが、メールの本文に含まれているかチェックする
- テストユーザーのアドレスがエスケープされて、メールの本文に含まれているかチェックする

上記を踏まえ実装する。
自動的に生成される`test "password_reset"`については、12章で実装するためコメントアウトする。
（残しておくとテストがredになってしまう）

```/sample_app/test/mailers/user_mailer_test.rb
class UserMailerTest < ActionMailer::TestCase
  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name, mail.body.encoded
    assert_match user.activation_token, mail.body.encoded
    assert_match CGI.escape(user.email), mail.body.encoded
  end
```

####【config】テストファイル内のドメイン名を設定する
テスト内でのドメインを設定する。

```/sample_app/config/environments/test.rb
  config.action_mailer.default_url_options = { host: 'example.com' }
```

####動作確認
mailerのテストがgreenになることを確認。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test:mailers
Started with run options --seed 17823

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

Finished in 0.63635s
1 tests, 9 assertions, 0 failures, 0 errors, 0 skips
```


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

テストスイートがgreenであることを確認。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 2015
Started with run options --seed 24199

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

Finished in 2.04996s
43 tests, 188 assertions, 0 failures, 0 errors, 0 skips
```

###演習2
*リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。*

`CGI.escape`部分をコメントアウトする。

```/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 (account-activation) $ rails test
Running via Spring preloader in process 2134
Started with run options --seed 58681

 FAIL["test_account_activation", UserMailerTest, 1.9940800210024463]
 test_account_activation#UserMailerTest (1.99s)
        Expected /michael@example\.com/ to match # encoding: US-ASCII
        "\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0\r\nContent-Type: text/plain;\r\n charset=UTF-8\r\nContent-Transfer-Encoding: 7bit\r\n\r\nHi Michael Example\r\n\r\nWelcome to the Sample App! Click on the link below to active your account:\r\n\r\nhttp://example.com/account_activations/pFlo06HSkw3Yug79-uZaQg/edit?email=michael%40example.com\r\n\r\n\r\n\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0\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>Sample App</h1>\r\n\r\n<p>Hi Michael Example</p>,\r\n\r\n<p>\r\n  Welcome to the Sample App! Click on the link below to activate your account:\r\n</p>\r\n\r\n<a href=\"http://example.com/account_activations/pFlo06HSkw3Yug79-uZaQg/edit?email=michael%40example.com\">Activate</a>\r\n\r\n  </body>\r\n</html>\r\n\r\n----==_mimepart_594c3c6d68deb_85612410e8528f0--\r\n".
        test/mailers/user_mailer_test.rb:14:in `block in <class:UserMailerTest>'

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

Finished in 2.15642s
43 tests, 188 assertions, 1 failures, 0 errors, 0 skips
```

動作確認後は、コメントアウト部分を元に戻しておく。

##11.2.4 ユーザーのcreateアクションを更新
###本章での学び
作成したメイラーを使うために、createアクションを修正する。

####【controller】ユーザー登録時に、アカウント有効化メールの送信処理を追加する
以下の処理を、

```/sample_app/app/controllers/users_controller.rb
    if @user.save
      log_in(@user)
      #保存の成功をここで扱う
      flash[:success] = "Welcome to the Sample App!"
      redirect_to user_url @user
    else

```

以下のように修正する。
`deliver_now`は、メールを送信するメソッド。
アカウント有効化メールを送信後、ログインするのではなく、TOPページにリダイレクトさせる

```/sample_app/app/controllers/users_controller.rb
    if @user.save
      UserMailer.account_activation(@user).deliver_now
      flash[:success] ="Please check your email to activate your account"
      redirect_to root_url
    else
```

####【test】統合テストの修正
現時点では、テストがredになってしまう。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1859
Started with run options --seed 60543

 FAIL["test_valid_signup_information", UsersSignupTest, 1.6497548810002627]
 test_valid_signup_information#UsersSignupTest (1.65s)
        expecting <"users/show"> but rendering with <["user_mailer/account_activation", "layouts/mailer", "static_pages/home", "layouts/_rails_default", "layouts/_shim", "layouts/_header", "layouts/_footer", "layouts/application"]>
        test/integration/users_signup_test.rb:37:in `block in <class:UsersSignupTest>'

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

Finished in 2.49370s
43 tests, 186 assertions, 1 failures, 0 errors, 0 skips
```

失敗するテストを一時的にコメントアウトする。

```/sample_app/test/integration/users_signup_test.rb
  test "valid signup information" do
    get signup_path
    assert_difference 'User.count', 1 do
      post users_path, params: { user: { name: "Example User",
                                          email: "user@example.com",
                                          password: "password",
                                          password_confirmation: "password" } }
    end
    follow_redirect!
    # assert_template 'users/show'
    # assert_not flash.empty?
    # assert is_logged_in?
  end
```

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

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails test
Running via Spring preloader in process 1971
Started with run options --seed 16030

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

Finished in 2.11845s
43 tests, 185 assertions, 0 failures, 0 errors, 0 skips
```

####動作確認
ブラウザから、ユーザー登録を行う。
（test2ユーザーで登録を実施）

![image.png](https://qiita-image-store.s3.amazonaws.com/0/98130/de62718e-8b93-9fd3-2757-59db9eaa6069.png)

コンソールにメール送信ログが表示されていることを確認。

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

Hi test2

Welcome to the Sample App! Click on the link below to active your account:

https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com



----==_mimepart_594c956b2e8ac_82a276bab859751
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>Sample App</h1>

<p>Hi test2</p>,

<p>
  Welcome to the Sample App! Click on the link below to activate your account:
</p>

<a href="https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com">Activate</a>

  </body>
</html>
```

###演習1
*新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?*

コンソールログをより確認。
リダイレクト先のURLが下記の通りルートURLに302で送信されていることを確認。

```
Redirected to https://rails-tutorial-yokoyan.c9users.io/
Completed 302 Found in 747ms (ActiveRecord: 21.9ms)
```

有効化トークンの値は、`authenticity_token"=>"BChjYdix9aSf0+wv9gPXrz3WoPJzZPKLVPE/bSqMMskkQkQ0cwXPbKmyr583JHM+0APDwxH37qT6+YbHNKWxwg==`

```irb
Started GET "/" for 125.199.218.217 at 2017-06-23 04:13:13 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
  ActiveRecord::SchemaMigration Load (0.3ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by StaticPagesController#home as HTML
  Rendering static_pages/home.html.erb within layouts/application
  Rendered static_pages/home.html.erb within layouts/application (320.5ms)
  Rendered layouts/_rails_default.html.erb (100.5ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (3.9ms)
  Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 447ms (Views: 436.8ms | ActiveRecord: 0.0ms)


Started GET "/signup" for 125.199.218.217 at 2017-06-23 04:13:17 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#new as HTML
  Rendering users/new.html.erb within layouts/application
  Rendered shared/_error_messages.html.erb (0.4ms)
  Rendered users/_form.html.erb (421.1ms)
  Rendered users/new.html.erb within layouts/application (422.4ms)
  Rendered layouts/_rails_default.html.erb (30.7ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (0.7ms)
  Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 473ms (Views: 460.4ms | ActiveRecord: 0.7ms)


Started POST "/signup" for 125.199.218.217 at 2017-06-23 04:13:30 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
  ActiveRecord::SchemaMigration Load (0.1ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by UsersController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"BChjYdix9aSf0+wv9gPXrz3WoPJzZPKLVPE/bSqMMskkQkQ0cwXPbKmyr583JHM+0APDwxH37qT6+YbHNKWxwg==", "user"=>{"name"=>"test2", "email"=>"test2@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create my account"}
   (0.1ms)  begin transaction
  User Exists (5.9ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "test2@example.com"], ["LIMIT", 1]]
  SQL (0.3ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?)  [["name", "test2"], ["email", "test2@example.com"], ["created_at", 2017-06-23 04:13:30 UTC], ["updated_at", 2017-06-23 04:13:30 UTC], ["password_digest", "$2a$10$ox4DtTzrIjUOsCTFUe3dg.aqbZXJ1taP0HF.v6cTr6C4DUs3zImTK"], ["activation_digest", "$2a$10$OhYHKhyL8LqTJ/r2livKRuRTVhWSfXROncbmcmHmmWVmpINb9xE.S"]]
   (14.9ms)  commit transaction
  Rendering user_mailer/account_activation.html.erb within layouts/mailer
  Rendered user_mailer/account_activation.html.erb within layouts/mailer (6.1ms)
  Rendering user_mailer/account_activation.text.erb within layouts/mailer
  Rendered user_mailer/account_activation.text.erb within layouts/mailer (0.4ms)
UserMailer#account_activation: processed outbound mail in 150.2ms
Sent mail to test2@example.com (8.9ms)
Date: Fri, 23 Jun 2017 04:13:31 +0000
From: noreply@example.com
To: test2@example.com
Message-ID: <594c956b30451_82a276bab859864@yokoyan-rails-tutorial-4550834.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_594c956b2e8ac_82a276bab859751";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


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

Hi test2

Welcome to the Sample App! Click on the link below to active your account:

https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com



----==_mimepart_594c956b2e8ac_82a276bab859751
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>Sample App</h1>

<p>Hi test2</p>,

<p>
  Welcome to the Sample App! Click on the link below to activate your account:
</p>

<a href="https://rails-tutorial-yokoyan.c9users.io/account_activations/oa_jXO-rDZ4i_xBqG2knXg/edit?email=test2%40example.com">Activate</a>

  </body>
</html>

----==_mimepart_594c956b2e8ac_82a276bab859751--

Redirected to https://rails-tutorial-yokoyan.c9users.io/
Completed 302 Found in 747ms (ActiveRecord: 21.9ms)


Started GET "/" for 125.199.218.217 at 2017-06-23 04:13:31 +0000
Cannot render console from 125.199.218.217! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
  Rendering static_pages/home.html.erb within layouts/application
  Rendered static_pages/home.html.erb within layouts/application (165.7ms)
  Rendered layouts/_rails_default.html.erb (50.5ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (3.6ms)
  Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 229ms (Views: 227.6ms | ActiveRecord: 0.0ms)
```

###演習2
*コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。*

コンソールから確認。
追加されたユーザーの有効化ステータスが、falseであることを確認。

```irb
yokoyan:~/workspace/sample_app (account-activation) $ rails console
Running via Spring preloader in process 2627
Loading development environment (Rails 5.0.0.1)
>> test2 = User.find_by(email: "test2@example.com")
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "test2@example.com"], ["LIMIT", 1]]
=> #<User id: 101, name: "test2", email: "test2@example.com", created_at: "2017-06-23 04:13:30", updated_at: "2017-06-23 04:13:30", password_digest: "$2a$10$ox4DtTzrIjUOsCTFUe3dg.aqbZXJ1taP0HF.v6cTr6C...", remember_digest: nil, admin: false, activation_digest: "$2a$10$OhYHKhyL8LqTJ/r2livKRuRTVhWSfXROncbmcmHmmWV...", activated: nil, activated_at: nil>
>> 
?> test2.activated?
=> false
>> 
```

#おわりに
メールの送信処理の組み込みが完了しました。
メールテンプレートの作成や、メール送信処理そのものも、
Railsだと簡単に実装できます。

有効化トークンを埋め込んだURLによる認証は、ECサイト等でも当たり前にある機能であるため、
仕組みが非常に勉強になりました。