Ruby on Rails チュートリアル 第11章 アカウント有効化(AcctionMailer Activation)やSendGridの使い方など

#近況報告

エンジニア転職成功しました。YouTubeもはじめました。

https://www.youtube.com/channel/UCcXQG8lu_EdyP2Cbhtz1bcA


[前回の続き](https://qiita.com/yuuki_netb/items/e2a12fac11e9ff344528)

著者略歴
著者：[YUUKI](https://twitter.com/bitcoinjpnnet)
ポートフォリオサイト：[Pooks](https://pooks.herokuapp.com/)
現在：RailsTutorial2周目

#第11章　アカウントの有効化　難易度 ★★★★★　10時間（エラー頻発の為）
<b>挫折しないRailsチュートリアルの進め方を先にお読みください↓↓</b>

[Railsチュートリアルで挫折しない３つのポイント](https://webnewage.com/rails-tutorial-point)

現時点では、新規登録したユーザーは初めから全ての機能にアクセスできるようになっている。
本章では、アカウントを有効化するステップを新規登録の途中に差し込むことで、本当にそのメールアドレスの持ち主なのか、確認できるようにする。

具体的には

①有効化トークンやダイジェストを関連付けておく
②有効化トークンを含めたリンクをユーザーにメールで送信
③ユーザーがそのリンクをクリックすると有効化

このような仕組みで、メールアドレスの持ち主であることを証明させる。

第12章でも似たような仕組みを使って、ユーザーがパスワードを忘れた時にパスワードを再設定できる仕組みを実装する。

これらの機能ごとに新しいリソースを作成し、コントローラ/ルーティング/データベース以降/の例について、１つずつ学んでいく。

最後に、Railsの開発環境や本番環境からメールを実際に送信する方法についても学ぶ。

アカウントを有効化する段取りは、ユーザーログイン&ユーザーの記憶と似ている。

①ユーザーの初期状態を「有効化されていない(unactivated)」にする
②ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する
③有効化ダイジェストはDBに保存。有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく
④ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、DB内に保存しておいた有効化ダイジェストと比較することで、トークンを認証する
⑤ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み(activated)」に変更する。

都合の良いことに、今回実装するアカウント有効化やパスワード再設定の仕組みと、以前に実装したパスワードや記憶トークンの仕組みにはよく似た点が多いので、多くのアイデアを使い回すことができる。

例えば、`User.digest`や`User.new_token`、改良版の`user.authenticated?`メソッドなど

検索キー、string、digest、authenticationごとの点

| 検索キー | string | digest | authentication |
|:--|:--|:--|:--|
| email | password | password_digest | authenticate(password) |
| id | remember_token | remember_digest | authenticated?(:remember, token) |
| email | activation_token | activation_digest | authenticated?(:activation, token) |
| email | reset_token | reset_digest | authenticated?(:reset, token) |

出典：[表 11.1: ログイン/記憶トークン/アカウントの有効化/パスワードの再設定で似ている点](https://railstutorial.jp/chapters/account_activation?version=5.1#cha-account_activation)

今章でアカウント有効化に必要なリソース、データモデルを作っていく。
アカウント有効化時のメール送信部分は
メイラーを使って作っていく。

また、authenticated?メソッドを使って、実際にアカウントを有効化する部分も実装していく。

##11.1 AccountActivationsリソース

8章で扱ったセッション機能を使って、アカウント有効化の作業をリソースとしてモデル化する。

アカウントの有効化リソースはActive Recordのモデルとは関係ないため、関連付けはしない。
その代わり、この作業に必要なデータ(有効化トークンや有効化ステータスなど）をUserモデルに追加する。


アカウント有効化もリソースとして扱いたいが、いつもとは少し使い方が異なる。

例えば、有効化用のリンクにアクセスして有効化のステータスを変更する部分では、RESTのルールに従うとPATCHリクエストとupdateアクションになるべき。

しかし、有効化リンクはメールでユーザーに送られる。
ユーザーがこのリンクをクリックすれば、それはブラウザで普通にクリックしたときと同じであり、その場合ブラウザから発行されるのは(updateアクションで使うPATCHリクエストではなく)GETリクエストになってしまう。
このため、ユーザーからのGETリクエストを受けるために、updateアクションではなく`editアクション`に変更して使っていく。


```ruby
$ git checkout -b account-activation
```

###11.1.1 AccountActivationsコントローラ

UsersリソースやSessionsリソースのときと同様に、AccountActivationsリソースを作るために、
まずは`AccountActivationsコントローラ`を生成する。

```ruby
$ rails g controller AccountActivations
```

ここで、有効化のメールには

```ruby
edit_account_activation_url(activation_token, ...)
```

これは、editアクションへの名前付きルートが必要になるということ。
そこで、まずは名前付きルートを扱えるようにするため、ルーティングにアカウント有効化用のresources行を追加する。

```rb:routes.rb
Rails.application.routes.draw do
  get 'sessions/new'

  get 'users/new'

  root 'static_pages#home'
  get     '/help',    to: 'static_pages#help'
  get     '/about',   to: 'static_pages#about'
  get     '/contact', to: 'static_pages#contact'
  get     '/signup',  to: 'users#new'
  post    '/signup',  to: 'users#create'
  get     '/login',   to: 'sessions#new'
  post    '/login',   to: 'sessions#create'
  delete  '/logout',  to: 'sessions#destroy'
  resources :users                                      # usersリソースをRESTfullな構造にするためのコード。
  resources :account_activations, only: [:edit]         # editアクションのみaccount_activationsリソースを適用
end
```

| HTTPリクエスト | URL | Action | 名前付きルート |
|:--|:--|:--|:--|
| GET | /account_activation/<token>/edit | edit | edit_account_activation_url(token) |

出典：[表 11.2: アカウント有効化用のRESTfulなルーティング設定 (リスト 11.1)](https://railstutorial.jp/chapters/account_activation?version=5.1#sec-account_activation_data_model)


これからアカウント有効化用のデータモデルとメイラーを作って行く。

####演習

1:テストがパスすることを確認

確認済み

2:名前付きルートでは、`_path`ではなく`_url`を使うように記してある。その理由は？

`_url`は(http://~)を指定してメールから飛べるようにしている。
(_pathだと相対パスな為http://〜からの指定ができない)

###11.1.2 AccountActivationのデータモデル

有効化のメールは一位の有効化トークンが必要。

例えば送信メールとデータベースのそれぞれに同じ文字列を置いておく方法がある。
しかし、この方法だとデータベースの内容が漏れた時、多大な被害に繋がってしまう。

攻撃者がDBへのアクセスに成功して、新しく登録されたユーザーアカウントの有効化トークンを盗み取り、本来のユーザーが使う前にそのトークンを使ってしまうケースなど、セキュリティが甘いケースに繋がってしまう。

このような事態を防ぐために、パスワードの実装や記憶トークンの実装と同じように、仮想的な属性を使って**ハッシュ化した文字列をDBに保存するようにする。**

具体的には

```ruby
user.activation_token
```

このようなコードで仮想属性の有効化トークンにアクセスし

```ruby
user.authenticated?(:activation, token)
```

Userモデルで編集した`authenticated?`メソッドを使って、`activated属性`を追加して論理値を取るようにする。

これで、自動生成の論理値メソッドと同じような感じで、ユーザーが有効であるかどうかをテストできるようになる。

```ruby
if user.activated?
```

ユーザーを有効化した時の、変更後のデータモデルはこうなる

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/19170caf-653a-1a47-e449-81f3bd6aa150.png)

出典：[図 11.1: Userモデルにユーザー有効化用の属性を追加する](https://railstutorial.jp/chapters/account_activation?version=5.1#sec-account_activation_data_model)

次のマイグレーションをコマンドラインで実行し、データモデルを追加すると、３つの属性が新しく追加される。

```ruby
rails g migration add_activation_to_users ¥
```

次に、admin属性の時と同様に、`activated属性`のデフォルトの論理値をfalseにしておく。


```[timestamp]_add_admin_to_users.rb
class AddActivationToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean, default: false
    add_column :users, :activated_at, :datetime
  end
end
```

マイグレーションを実行して変更を反映。

```ruby
$ rails db:migrate
```

####Activeトークンのコールバック

ユーザーが新しい登録を完了するためには必ずアカウントの有効化が必要になる。
その為、「有効化トークン」や「有効化ダイジェスト」はユーザーオブジェクトが作成される前に、生成されるようにしておく必要がある。

メールアドレスをDB保存時も、保存前に全部小文字に変換するようにしたが、その時は`before_save`コールバックに`downcase`メソッドをバインドした。

オブジェクトにbefore_saveコールバックを用意しておくと、オブジェクトが保存される直前、オブジェクトの作成時や更新時にそのコールバックが呼び出される。

しかし、**今回はオブジェクトが作成された時だけコールバックを呼び出したい。**
（それ以外の時は呼び出したくない）

そこで、`before_create`コールバックを使う。

```ruby
before_create :create_activation_digest
```

このコードは`メソッド参照`と呼ばれるもので、こうするとRailsは`create_activation_digest`というメソッドを探し、ユーザーを作成する前に実行するようになる。

6章では、`before_save`に明示的にブロックを渡していたが、メソッド参照の方がおすすめ。

また、create_activation_digest（作成した有効化記憶トークンメソッド）メソッド自体はUserモデル内でしか使わない為、`private`キーワードの中に書く。

```ruby
private

   def create_activation_digest
      # 有効化トークンとダイジェストを作成および代入する
   end
```

クラス内でprivateキーワードより下に記述したメソッドが非公開なのは、コンソールで確かめられる。

```ruby
>> User.first.create_activation_digest
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
NoMethodError: private method `create_activation_digest' called for #<User:0x007f8da4098e98>
Did you mean?  restore_activation_digest!
        from (irb):1
```

今回は`before_create`コールバックを使う目的は、トークンとそれに対応するダイジェストを割り当てるためである。

```ruby
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
```

このコードでは、記憶トークンや記憶ダイジェストのために作ったメソッドを使いまわしている。
9章で扱った`remember`メソッドと比べてみる。

```ruby
# 永続セッションのためにユーザーをデータベースに記憶する
def remember
  self.remember_token = User.new_token
  update_attribute(:remember_digest, User.digest(remember_token))
end
```

主な違いは、後者の`update_attribute`の使い方にある。

この違いは、記憶トークンやダイジェストは既にDBにいるデータベースのために作成されるのに対し、`before_create`コールバックの方はユーザーが作成される前に呼び出される。

このコールバックがあることで、`User.new`で新しいユーザーが定義されると、`activation_token`属性や`activation_digest`属性が得られるようになる。

後者の`activation_digest`属性は既にDBのカラムとの関連付けができあがっているので、ユーザーが保存されるときに一緒に保存される。
　

上記の説明をUserモデルに実装してみる。

```rb:user.rb
class User < ApplicationRecord
  # インスタンス変数の定義
  attr_accessor :remember_token , :activation_token                             # 記憶トークンと有効化トークンを定義
  before_save :downcase_email                                                   # DB保存前にemailの値を小文字に変換する
  before_create :create_activation_digest                                       # 作成前に適用

private

  # メールアドレスを全て小文字にする

  def downcase_email
    self.email = email.downcase                                                 # emailを小文字化してUserオブジェクトのemail属性に代入
  end
  
  # 有効化トークンとダイジェストを作成および代入する
    
  def create_activation_digest
    self.activation_token   =   User.new_token                                  # ハッシュ化した記憶トークンを有効化トークン属性に代入
    self.activation_digest  =   User.digest(activation_token)                   # 有効化トークンをBcryptで暗号化し、有効化ダイジェスト属性に代入
  end
```

####サンプルユーザーの作成とテスト

seedsファイルを更新しサンプルユーザーを作成、
fixtureを更新してテスト時のサンプルユーザーを事前に作成しておく。

```rb:seeds.rb
User.create!(name:                    "Example User",
             email:                   "example@railstutorial.org",
             password:                "foobar",
             password_confirmation:   "foobar",
             admin:     true,
             activated: true,
             activated_at: Time.zone.now )

99.times do |n|
  name          = Faker::Name.name
  email         = "example-#{n+1}@railstutorial.org"
  password      = "password"
  User.create!(name:  name,
              email: email,
              password:              password,
              password_confirmation: password,
              activated: true,
              activated_at: Time.zone.now)
end
```

`Time.zone.now`はRailsの組み込みヘルパーであり、サーバーのタイムゾーンに応じたタイムスタンプを返す。


```yml:users.yml
michael:
  name: Michael Example
  email: michael@example.com
  password_digest: <%= User.digest('password') %>
  admin: true
  activated: true
  activated_at: <%= Time.zone.now %>

archer:
  name: Sterling Archer
  email: duchess@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

lana:
  name: Lana Kane
  email: hands@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

malory:
  name: Malory Archer
  email: boss@example.gov
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>

<% 30.times do |n| %>
user_<%= n %>:
  name:  <%= "User #{n}" %>
  email: <%= "user-#{n}@example.com" %>
  password_digest: <%= User.digest('password') %>
  activated: true
  activated_at: <%= Time.zone.now %>
<% end %>
```

DBを初期化して、サンプルデータを再度生成し直し、変更を反映

```ruby
$ rails db:migrate:reset
$ rails db:seed
```

####演習

1:テストが通ることを確認

確認済み

2:コンソールからUserクラスのインスタンスを生成し、そのオブジェクトから`create_activation_digest`メソッドを呼び出そうとすると`NoMethodError`が発生することを確認。また、そのUserオブジェクトからダイジェストの値も確認。

```ruby
>> user = User.new
>> user.create_activation_digest
NoMethodError: private method `create_activation_digest' called for #<User:0x000000045483b0>
Did you mean?  restore_activation_digest!
        from (irb):2
>> user.digest
NoMethodError: undefined method `digest' for #<User:0x000000045483b0>
        from (irb):3
>> user.activation_digest
=> nil
```

3:6章で、`email.downcase!`を使いemail属性を変更する方法を学んだ。(破壊的メソッド)
このメソッドを使って、user.rbの`downcase_email`メソッドを改良してみる。

```rb:user.rb
  def downcase_email
    email.downcase!                                                             # emailを小文字化してUserオブジェクトのemail属性に代入
  end
```

```ruby
$ rails t
11 tests, 15 assertions, 0 failures, 0 errors, 0 skips
```

テストがパスしたのでOK。


##11.2 アカウント有効化のメール送信

データのモデル化が終わったので、今度はアカウント有効化メールの送信に必要なコードを追加する。

このメソッドではAction Mailerライブラリを使ってUserのメイラーを追加する。
このメイラーはUsersコントローラのcreateアクションで有効化リンクをメールで送信するために使う。

メイラーの構成はコントローラのアクションとよく似ており、メールのテンプレートをビューと同じ要領で定義する。
このテンプレートの中に有効化トークンと、メールアドレスを有効にするアカウントのアドレスのリンクを含め、使っていく。

###11.2.1 送信メールのテンプレート

メイラーは、モデルやコントローラと同様に`rails generate`で生成できる。

```ruby
$ rails g mailer UserMailer account_activation password_reset
```

このコマンドを実行したことにより、今回必要となる`account_activation`メソッドと、次章で必要となる`password_reset`メソッドが生成された。

また、上記のコマンドは生成したメイラーごとに、**ビューのテンプレートが２つずつ生成される**

アカウント有効化メイラーのビュー２つ

①テキストメール用のテンプレート
②HTMLメール用のテンプレート


アカウント有効化に使うテンプレートを確認してみる。

```erb:account_activation.text.erb
User#account_activation

<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
```

```erb:account_activation.html.erb
<h1>User#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
```

また、mailerディレクトリの中に生成されたメイラーファイル`application_mailer.rb`と`user_mailer.rb`の２つも確認。

この２つのファイルはメールの動きを設定する(モデルで言うコントローラみたいなもの)

```rb:application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'from@example.com'
  layout 'mailer'
end
```

```rb:user_mailer.rb
class UserMailer < ApplicationMailer

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

    mail to: "to@example.org"
  end

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

application_mailerでは、アプリケーション全体で共通のデフォルトのfromアドレスがある。
user_mailerでは`mail to:`にて宛先のメールアドレスを設定している。

また、メールフォーマットに対応するメイラーレイアウトも使われている。
生成されるHTMLメイラーのレイアウトやテキストメイラーのレイアウトは`app/views/layouts`で確認できる。

生成されたコードにはインスタンス変数`@greeting`も含まれている。

このインスタンス変数は、丁度普通のビューでコントローラのインスタンス変数を利用できるのと同じように、メイラービューで利用できる。


最初に、生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにする。

次に、ユーザーを含むインスタンス変数を作成してビューで使えるようにし、`user.email`にメールを送信する。
`user_mailer.rb`では、mailにsubjectキーを引数として渡している。この値は、**メールの件名**にあたる。

```rb:application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: 'noreply@example.com'
  layout 'mailer'
end
```

```rb:user_mailer.rb
class UserMailer < ApplicationMailer

  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end

  def password_reset
    @greeting = "Hi"

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


テンプレートビューは、通常のビューと同様ERBで自由にカスタマイズできる。

ここでは挨拶文にユーザー名を含め、カスタムの有効化リンクを追加する。
この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにしたいので、リンクにはメールアドレスとトークンを両方含めておく必要がある。

AccountActivationsリソースで有効化をモデル化したので、トークン自体は名前付きルートの引数で使われる。

```ruby
edit_account_activation_url(@user.activation_token, ...)
```

例えば

```ruby
edit_user_url(user)
```

上のメソッドは、絶対パスの`user_url`でurlを生成し、引数のユーザーの編集ページにアクセスする
つまり

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

上記のURLの`q5lt38hQDc_959PVoo6b7A`という部分は`new_token`メソッドで生成されたもの。

URLで使えるようにBase64でエンコードされている。
これは丁度`/users/1/edit`の1のようなユーザーIDと同じ役割を果たす。

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

クエリパラメータを使って、このURLにメールアドレスをうまく組み込んでみる。

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

この時、メールアドレスの@は%40と言う文字にエスケープしている。
(@は通常URLでは扱えない)

Railsでクエリパラメータを設定するには、名前付きルートに対して、次のようなハッシュを追加しする。

```ruby
edit_account_activation_url(@user.activation_token, email: @user.email)
```

このようにして名前付きルートでクエリパラメータを定義すると、Railsが特殊な文字を自動的にエスケープしてくれる。

コントローラで`params[:email]`からメールアドレスを取り出す時には、自動的にエスケープを解除してくれる。

ここまでできれば、user_mailerで定義した@userインスタンス変数、editへの名前付きルート、ERBを組み合わせて、必要なリンクを作成できる。

アカウント有効化のHTMLテンプレートでは、正しいリンクを組み立てるために`link_to`メソッドを使われている。

```erb:account_activation.text.erb
こんにちは <%= @user.name %>,

YUUKIのポートフォリオへようこそ！下記リンクをクリックしたらあなたのアカウントは有効化されます♫:

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

```erb:account_activation.html.erb
<h1>YUUKIのポートフォリオ</h1>

<p>こんにちは <%= @user.name %>,</p>

<p>
 YUUKIを有効化する:
</p>

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

####演習

1:コンソールを開き、CGIモジュールのescapeメソッドでメールアドレスの文字列をエスケープできることを確認してみる。
このメソッドで`Don't panic!`をエスケープすると、どんな結果になるか？

```ruby
> CGI.escape('foo@example.com')
=> "foo%40example.com"
>> 
```

###11.2.2 送信メールのプレビュー

テンプレートで定義した実際の表示を確認するため、**メールプレビュー**という裏技を使ってみる。

Railsでは、**特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができる。**

メールを実際に送信しなくてもいいので大変便利。
これを利用するには、アプリケーションのdevelopment環境（開発環境）の設定に手を加える必要がある。

```rb:development.rb
Rails.application.configure do
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'us-east-2.console.aws.amazon.com'
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
```

このように、host名のとこに自分の開発環境のホスト名を記入する。

developmentサーバーを再起動してdevelopmentの設定を読み込んだら、次はUserメイラーのプレビューファイルを更新する。

```rb:user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/account_activation
  def account_activation
    UserMailer.account_activation
  end

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end

end
```

`user_mailer.rb`ファイルで指定した`account_activation(user)`の引数には有効なUserオブジェクトを渡す必要があるため、このままではプレビューファイルは動かない。
これを回避するため、user変数が開発用データベースの最初のユーザーになるよう定義して、それを`UserMailer.account_activation_token`の引数として渡す。

```rb:user_mailer_preview.rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/account_activation
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    UserMailer.account_activation(user)
  end

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end

end
```

ここで、user.activation_tokenの値にも代入している点に注目。
アカウント有効化ビューのテンプレートでは、アカウント有効化のトークンが必要なので、代入は省略できない。

なお、`activation_token`は仮の属性でしかないので、DBのユーザーはこの値を実際には持っていない（ダイジェストしかない）

![スクリーンショット 2019-01-21 13.29.20.png](https://qiita-image-store.s3.amazonaws.com/0/244639/c841bbc0-6034-b941-679f-e1b20ee18ee6.png)

![スクリーンショット 2019-01-21 14.06.23.png](https://qiita-image-store.s3.amazonaws.com/0/244639/c8d2dd05-b77c-18e9-ca8f-40154304cbc7.png)

####演習

1:Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみる。
Dateの欄にはどんな内容が表示されているか？

アクセスした時刻が表示されている。

###11.2.3 送信メールのテスト

　最後に、このメールプレビューのテストも作成して、プレビューをダブルチェックできるようにする。
mailer作成時にテストも自動で生成されているので、これを利用すればテストの作成は簡単。

```rb:user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase
  test "account_activation" do
    mail = UserMailer.account_activation
    assert_equal "Account activation", mail.subject
    assert_equal ["to@example.org"], mail.to
    assert_equal ["from@example.com"], mail.from
    assert_match "Hi", mail.body.encoded
  end

  test "password_reset" do
    mail = UserMailer.password_reset
    assert_equal "Password reset", mail.subject
    assert_equal ["to@example.org"], mail.to
    assert_equal ["from@example.com"], mail.from
    assert_match "Hi", mail.body.encoded
  end

end
```

上記のテストでは、`assert_match`というメソッドが使われており、これを使えば**正規表現で文字列をテストできる**

```ruby
assert_match 'foo',   'foobar'  #true
assert_match 'baz',   'foobar'  #false
assert_match '/¥w+/', 'foobar'  #true
assert_match '/¥w+/', '$#!*+@'  #false
```

assert_matchメソッドを使って、名前・有効化トークン・エスケープ済みメールアドレスがメール本文に含まれているかどうかをテストする。

また、

```ruby
CGI.escape(user.email)
```

こうすることで、引数に取ったemailをエスケープ処理することができる。

```rb:user_mailer_test.rb
require 'test_helper'

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

なお、このテストではまだ失敗する。

上記テストコードでは、fixtureユーザーに有効化トークンを追加している点に注目。
(user.activation_token = のところ)
追加しない場合は、空白になる。

なお、生成されたパスワード設定のテストも削除しているが、のちに戻す。

このテストをパスさせるには、テストファイル内のドメイン名を正しく設定する必要がある。

```rb:test.rb
  config.action_mailer.delivery_method = :test
  config.action_mailer.default_url_options = { host: 'example.com' }
```

これでテストはパスする。

```ruby
$ rails t
1 tests, 9 assertions, 0 failures, 0 errors, 0 skips
```

*ここで注意だが、`account_activation.html`にて、pタグで長く名付けると`assert_match`でテストが失敗する。

例

```rb:account_activation.html.rb
<p>
 YUUKIのポートフォリオへようこそ！下記リンクをクリックしたらあなたのアカウントは有効化されます：
</p>
```

失敗

```rb:account_activation.html.rb
<p>
 有効化:
</p>
```

成功

2:CGI.escapeの部分を削除するとテストが失敗することを確認

確認済み

###11.2.4 ユーザーのcreateアクションを更新


あとはユーザー登録を行う`create`アクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことができる。

```rb:users_controller.rb
  def create
    @user = User.new(user_params)                                               # newビューにて送ったformの中身(nameやemailの値)をuser_paramsで受け取り、ユーザーオブジェクトを生成、@userに代入
    if @user.save
      UserMailer.account_activation(@user).deliver_now                          # アカウント有効化メールの送信
      flash[:info] = "メールを確認してアカウントを有効化してね"                 # アカウント有効化メッセージの表示
      redirect_to root_url                                                      # ホームへ飛ばす
    else
      render 'new'
    end
  end
```

ここで、`UserMailer`を使って入力されたメールアドレス宛にアカウント有効化のメッセージを送っている点と、
ユーザー登録時にリダイレクト先をroot_urlへ飛ばしてる点に、
ログインしないように変更した点に注目。

登録時のリダイレクトの挙動が変更されたため、テストは失敗する。

そこで、該当箇所のテストはとりあえずコメントアウトしておく。

```rb:users_signup_test.rb
  test "valid signup information" do                                            # 新規登録が成功（フォーム送信）したかのテスト
    get signup_path                                                             # signup_path(/signup)ユーザー登録ページにアクセス
    assert_difference 'User.count', 1 do                                        # User.countでユーザー数をカウント、1とし、ユーザー数が変わったらtrue、変わってなければfalse
      post users_path, params: { user: { name:                 "Example User",  # signup_path(/signup)からusers_path(/users)へparamsハッシュのuserハッシュの値を送れるか検証
                                        email:                 "user@example.com",
                                        password:              "password",
                                        password_confirmation: "password" } }
    end
    follow_redirect!                                                            # 指定されたリダイレクト先(users/show)へ飛べるか検証
    #assert_template 'users/show'                                                # users/showが描画されているか確認
    assert_not   flash.blank?                                                   # flashが空ならfalse,空じゃなければtrue
    #assert is_logged_in?                                                        # 新規登録時にセッションが空じゃなければtrue
  end
```

この状態で実際に新規ユーザーとして登録してみる。

![スクリーンショット 2019-01-22 15.07.42.png](https://qiita-image-store.s3.amazonaws.com/0/244639/286036e2-12a6-d00d-1789-931ebb3cc0a4.png)

サーバーログ(rails sの画面)を確認

```ruby
UserMailer#account_activation: processed outbound mail in 177.1ms
Sent mail to itotasuku2@gmail.com (6.6ms)
Date: Tue, 22 Jan 2019 06:07:32 +0000
From: noreply@example.com
To: ●●@gmail.com
Message-ID: <5c46b32415fe1_3ed625687987001@ip-172-31-25-8.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
 boundary="--==_mimepart_5c46b32414b75_3ed625687986994e";
 charset=UTF-8
Content-Transfer-Encoding: 7bit


----==_mimepart_5c46b32414b75_3ed625687986994e
```

おぉ、アカウント有効化メールアドレスが表示されている。

ただし、これは実際にメールが生成されるわけではないので注意。
のちに実際のメールを送信する方法を詳解する。

####演習

1:新規ユーザー登録でリダイレクト先が適切なURLに変わったことを確認。その後、Railsサーバーのログから送信メールの有効化トークンの値を確認。

```ruby
Redirected to https://eac437457e484fe491559aaa135f7f93.vfs.cloud9.us-east-2.amazonaws.com/
Completed 302 Found in 470ms (ActiveRecord: 11.3ms)
```

リダイレクト先がroo_urlの指示通り、ホームページへリダイレクトできている。

```ruby
"authenticity_token"=>"GqyypcjNdXgYvimMg9lCL6tXBhcYqsMIBqIA6R9EkgU+34HzSxxH4TmLun6dMaeiM9RwFfaImLGm5KspzMKxNg=="
```

有効化トークン(authenticity_token)がハッシュ化されている


2:コンソールを開き、DB常にユーザーが作成されたことを確認。
また、ユーザーはDB上にはいるが、有効化のステータスがfalseのままになっていることを確認。

```ruby
>> user = User.find(101)
>> user.activated?
=> false
```

##11.3 アカウントを有効化する

メールが生成できたら、今度はAccountActivationsコントローラのeditアクションを書いていく。

また、アクションへのテストを書き、しっかりとテストできていることが確認できたら、AccountActivationsコントローラからUserモデルにコードを移していく。

###11.3.1 authenticated?メソッドの抽象化

有効化トークンとメールはそれぞれ

`params[:id]`
`params[:email]`

で参照できる。

なので、パスワードのモデルと記憶トークンで学んだことを元に、次のようなコードでユーザーを検索して認証することにする。

```ruby
user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])
```

上のコードで使っている`authenticated?`メソッドは、アカウント有効化のダイジェストと、渡されたトークンが一致するかどうかをチェックしている。

ただし、このメソッドは記憶トークン用なので、今は正常に動作しない。

```ruby
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
```

`remember_digest`はUserモデルの属性なので、モデル内では次のように置き換えることができる。

```ruby
self.remember_digest
```

今回は、上記のコードの`remember`の部分をどうにかして変数として扱いたい。
つまり、次のコードの例のように、状況に応じて呼び出すメソッドを切り替えたい。

```ruby
self.FOOBAR.digest
```

これから実装する`authenticated?`メソッドでは、受け取ったパラメータに応じて呼び出すメソッドを切り替える手法を使う。

この手法を**メタプログラミング**と呼ぶ。

今回は`send`メソッドを用いてペアプログラミングを行う。

このメソッドは、渡されたオブジェクトにメッセージを送ることによって、呼び出すメソッドを動的に決めることができる。

Railsコンソールでやってみる。

まずは、Rubyのオブジェクトに対してsendメソッドを実行し、配列の長さを得る。

```ruby
$ rails c
                                                                Running via Spring preloader in process 19959
Loading development environment (Rails 5.1.6)
>> a = [1,2,3]
=> [1, 2, 3]
>> a.length
=> 3
>> a.send(:length)
=> 3
>> a.send("length")
=> 3
```

この時、sendを通してシンボルの`:length`や文字列の`"length"`は、いずれも`length`メソッドと同じ結果になった。つまり、どちらもオブジェクトにlengthメソッドを渡しているため、等価である。

もう１つの例は、DBの最初のユーザーが持つ`activation_digest`属性にアクセスする。

```ruby
$ rails c
Running via Spring preloader in process 7511
Loading development environment (Rails 5.1.6)
>> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-01-20 02:17:40", updated_at: "2019-01-20 02:17:40", password_digest: "$2a$10$ambbgHUgH.09zBb8AbfXqOPn2//.8cblJ2qQKEsPXyA...", remember_digest: nil, admin: true, activation_digest: "$2a$10$Hv0qwXYkDj7k6huuD.Kfguk4/2eyMimSbllKnJ0rIJv...", activated: true, activated_at: "2019-01-20 02:17:40">
>> user.activation_digest
=> "$2a$10$Hv0qwXYkDj7k6huuD.Kfguk4/2eyMimSbllKnJ0rIJvJKstLZfLba"
>> user.send(:activation_digest)
=> "$2a$10$Hv0qwXYkDj7k6huuD.Kfguk4/2eyMimSbllKnJ0rIJvJKstLZfLba"
>> user.send("activation_digest")
=> "$2a$10$Hv0qwXYkDj7k6huuD.Kfguk4/2eyMimSbllKnJ0rIJvJKstLZfLba"
>> attribute = :activation
=> :activation
>> user.send("#{attribute}_digest")
=> "$2a$10$Hv0qwXYkDj7k6huuD.Kfguk4/2eyMimSbllKnJ0rIJvJKstLZfLba"
>> 
```

最後の例では、シンボル`:activation`と等しい`attribute`変数を定義し、文字列の式展開(interpolation)を使って引数を正しく組み立ててから、sendに渡している。

文字列`activation`でも同じことができるが、Rubyではシンボルを使うのが一般的。

```ruby
"#{attribute}_digest"
```

シンボルと文字列どちらを使った場合でも、上のコードでは次のように文字列に変換される。

```ruby
"activation_digest"
```

sendメソッドの動作原理を理解した所で、この仕組みを利用して`authenticated?`メソッドを書き換えてみる。


```ruby
def authenticated?(remember_token)
  digest = self.send("remember_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(remember_token)
end
```

上のコードの各引数を一般化し、文字列の式展開も利用すると、次のようなコードになる。

```ruby
def authenticated?(attritubte, token)
 digest = self.send("#{attritubte}_digest")
 return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end
```

他の認証でも使えるように、上では2番目の引数tokenの名前を変更して一般化している点に注意。

また、このコードはモデル内にあるのでselfは省略することもできる。
最終的にRubyらしく書かれたコードは以下

```ruby
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
```

ここまでできれば、次のように呼び出すことで`authenticated?`の従来の振る舞いを再現できる。

```ruby
user.authenticated?(:remember, remember_token)
```

抽象化したauthenticated?メソッドをUserモデルに書いてみる。

```rb:user.rb
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end
```

この時点ではテストは失敗する。

テストが失敗する理由は`current_user`メソッドとnilダイジェストのテストの両方で、
`authenticated?`が古いママとなっており、さらに引数も２つではなくまだ１つのままだから。

これを解消するため、両者を更新して、**新しい一般的なメソッド**を使うようにする。

```rb:sessions_helper.rb
  def current_user
    if (user_id = session[:user_id])                                            # 一時的なセッションユーザーがいる場合処理を行い、user_idに代入
      @current_user ||= User.find_by(id: user_id)                               # 現在のユーザーがいればそのまま、いなければsessionユーザーidと同じidを持つユーザーをDBから探して@current_user（現在のログインユーザー）に代入
    elsif (user_id = cookies.signed[:user_id])                                  # user_idを暗号化した永続的なユーザーがいる（cookiesがある）場合処理を行い、user_idに代入
      user = User.find_by(id: user_id)                                          # 暗号化したユーザーidと同じユーザーidをもつユーザーをDBから探し、userに代入
        if user && user.authenticated?(:remember, cookies[:remember_token])     # DBのユーザーがいるかつ、受け取った記憶トークンを暗号化した記憶ダイジェストと、remember値が入ってるユーザーがいる場合処理を行う
        log_in user                                                             # 一致したユーザーでログインする
        @current_user = user                                                    # 現在のユーザーに一致したユーザーを設定
        end
    end
  end
```

```rb:user_test.rb
  test "authenticated? should return false for a user with nil digest" do       # authenticatedメソッドで記憶ダイジェストを暗号化できるか検証
    assert_not @user.authenticated?(:remember, '')                              # @userのユーザーの記憶ダイジェストと、引数で受け取った値が同一ならfalse、異なるならtrueを返す
  end
```

上記の変更を加えたらテストは成功する。

```ruby
$ rails t
11 tests, 15 assertions, 0 failures, 0 errors, 0 skips
```

このようなリファクタリングを施すとエラーが発生しやすくなるので、しっかりしたテストスイートが不可欠。


####演習

1:コンソール内でユーザーを作成してみる。
新しいユーザーの記憶トークンと有効化トークンはどのような値になっているか？
また、各トークンに対応するダイジェストの値はどうなっているか？

```ruby
>> user = User.create(name: "tesuto4", email: "tesuto@test.co.jp", password: "tesuto4", password_confirmation: "tesuto4")                      
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "tesuto@test.co.jp"], ["LIMIT", 1]]
  SQL (2.8ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?)  [["name", "tesuto4"], ["email", "tesuto@test.co.jp"], ["created_at", "2019-01-23 04:17:30.872700"], ["updated_at", "2019-01-23 04:17:30.872700"], ["password_digest", "$2a$10$Edcro0emsnf9BuYd0WXzMuHYGxzKI1AFiBh.kPtLz6qc81Okuf0by"], ["activation_digest", "$2a$10$d9vrDK5aEZCfFgaQIE/CUuLjRjrjAWHLomBJ/7u.4PdumyRNGqJQ2"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 102, name: "tesuto4", email: "tesuto@test.co.jp", created_at: "2019-01-23 04:17:30", updated_at: "2019-01-23 04:17:30", password_digest: "$2a$10$Edcro0emsnf9BuYd0WXzMuHYGxzKI1AFiBh.kPtLz6q...", remember_digest: nil, admin: false, activation_digest: "$2a$10$d9vrDK5aEZCfFgaQIE/CUuLjRjrjAWHLomBJ/7u.4Pd...", activated: false, activated_at: nil>
>> user.remember_token
=> nil
>> user.activation_token
=> "l39qT2c2s5LXb1r5C3OmjQ"
>> user.remember_digest
=> nil
>> user.activation_digest
=> "$2a$10$d9vrDK5aEZCfFgaQIE/CUuLjRjrjAWHLomBJ/7u.4PdumyRNGqJQ2"
```

アカウント有効化用のトークン・ダイジェストは生成されたが、ユーザーが有効化していないので、記憶トークン/記憶ダイジェスト値はnil。


2:authenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認。


```ruby
>> user.remember_token = User.new_token
=> "JfMcsK1wbYkz8L_uG2kzhA"
>> user.update_attribute(:remember_digest, User.digest(user.remember_token))
   (0.2ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2019-01-23 04:37:39.302291"], ["remember_digest", "$2a$10$WiFAB0sIsY9pXkoioQ9nDeb3Z0VbdJbqZTe3kUWN7fAavjo5YOY.a"], ["id", 102]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
```

まず記憶トークンと記憶ダイジェストを生成。

```ruby
>> user.authenticated?(:remember,user.remember_token)
=> true
```

引数に渡したダイジェスト値と、トークン値が一致したらtrue


trueが返ってきたので、認証が成功した。
(ログインが成功した)


###11.3.2 editアクションで有効化

`authenticated?`メソッドを完成させたので、editアクションを書いてみる。

このアクションでは、paramsハッシュで渡されたメールアドレスに対応するユーザーを認証する。

ユーザーが有効であることを確認する中核派、次の部分になる。

```ruby
if user && !user.activated? && user.authenticated?(:activation, params[:id])
```

`!user.activated?`という記述に注目。

このコードは既に有効になっているユーザーを誤って再度有効化しないために必要。
正当であろうとなかろうと、有効化が行われるとユーザーはログイン状態になる。

もしこのコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまう。

そうした攻撃を防ぐためにこのコードは非常に重要。

上記の論理値に基づいてユーザーを認証するには、ユーザーを認証してから`activated_at`タイムスタンプを更新する必要がある。

```ruby
user.update_attribute(:activated,    true)
user.update_attribute(:activated_at, Time.zone_now)
```

上記のコードを`edit`アクションで使う。
これで日付更新ができる。

```rb:account_activations_controller.rb
class AccountActivationsController < ApplicationController
  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.update_attribute(:activated,     true)
      user.update_attribute(:activated_at,  Time.zone.now)
      log_in user
      flash[:success] = "アカウントを有効にしたンゴよ〜"
      redirect_to user
    else
      flash[:danger] = "きみはまだまだだね^^"
      redirect_to root_url
    end
  end
end
```

有効化のアクションを書いたら、実際にアカウントを有効化させてみる。

ブラウザでユーザーを新規登録後、rails sに載っているURLを貼り付けて有効化ページにアクセスする。

![スクリーンショット 2019-01-24 9.01.05.png](https://qiita-image-store.s3.amazonaws.com/0/244639/beec7d0e-e998-5bb1-a48a-47b548515fe1.png)


この状態ではユーザーのログイン方法を変更していないのでこれでは何の意味もない。
ということで、ユーザーの有効化を行う為に、
**ユーザーが有効である場合のみログインできるようにログイン方法を変更する必要がある。**

　これを行うには、`user.activated?`がtrueの場合にのみログインを許可し、
そうでない場合はルートURLにリダイレクトして`warning`で警告を表示する。

```rb:sessions_controller.rb
  # ユーザーを新規作成する
  
  def create
    @user = User.find_by(email: params[:session][:email].downcase)              # paramsハッシュで受け取ったemail値を小文字化し、email属性に渡してUserモデルから同じemailの値のUserを探して、user変数に代入
    if @user && @user.authenticate(params[:session][:password])                 # user変数がデータベースに存在し、なおかつparamsハッシュで受け取ったpassword値と、userのemail値が同じ(パスワードとメールアドレスが同じ値であれば)true
      if @user.activated?                                                       # userが有効の処理
        log_in @user                                                            # sessions_helperのlog_inメソッドを実行し、sessionメソッドのuser_id（ブラウザに一時cookiesとして保存）にidを送る
        params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) # ログイン時、sessionのremember_me属性が1(チェックボックスがオン)ならセッションを永続的に、それ以外なら永続的セッションを破棄する
        redirect_back_or @user                                                  # userの前のページもしくはdefaultにリダイレクト
      else                                                                      # userが有効でない処理
        message   = "アカウントは有効ではありません"
        message  += "メールで送られたURLから有効化してね"
        flash[:warning] = message
        redirect_to root_url
      end
    else
      flash.now[:danger] = 'Invalid email/password combination'                 # flashメッセージを表示し、新しいリクエストが発生した時に消す
      render 'new'                                                              # newビューの出力
    end
  end
```

![スクリーンショット 2019-01-24 19.07.29.png](https://qiita-image-store.s3.amazonaws.com/0/244639/f8200694-0bbf-9aad-22be-bdc5155d3267.png)


####演習　

1:コンソールからメールのURLを調べて、有効化トークンはどれか確認する。

```ruby
<a href="https://eac437457e484fe491559aaa135f7f93.vfs.cloud9.us-east-2.amazonaws.com/account_activations/cDACvOyopUBbsOnFsIr2sg/edit?email=tyaou%40example.com">Activate</a>
```

これの`cDACv〜`の部分。

2:URLから有効化リンクに飛んでユーザーの認証に成功し、有効化できることを確認する。
また、有効化ステータスがtrueになるかも確認。

```ruby
$ rails c
Running via Spring preloader in process 29877
Loading development environment (Rails 5.1.6)
>> user = User.find(110)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 110], ["LIMIT", 1]]
=> #<User id: 110, name: "tyaou", email: "tyaou@example.com", created_at: "2019-01-24 10:06:17", updated_at: "2019-01-24 10:06:27", password_digest: "$2a$10$7/vxN5esSjb1gxWrtZb9K.vkYnnbL/GsI5JwMx9/Kqh...", remember_digest: nil, admin: false, activation_digest: "$2a$10$Ty2/gM.jACJf3ll87jO9gO/hrVRkdgRHZdx57lakxbm...", activated: true, activated_at: "2019-01-24 10:06:27">
>> user.activated
=> true
```

OK。


####有効化に半日格闘した

何回やってもeditアクションの
`if user && !user.activated? && user.authenticated?(:activation, params[:id])`

の`autenticated?`の部分がtrueにならずに色々試行錯誤したのだが、
結果、`account_activation.html.erb`を全部日本語から英語にしたら直った。

```ruby
<h1>YUUKI</h1>

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

<p>
 activate:
</p>

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

こうしたってこと。

日本語だとメイラーのHTMLが文字化けしてURLが正しく発行されないので注意。
まじで疲れたよ・・・


###11.3.3 有効化のテストとリファクタリング

アカウント有効化の統合テストを追加する。

正しい情報でユーザー登録を行った場合のテストは既にあるので、7章で書いたテストに若干手を加える。

```rb:users_signup_test.rb
require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  # test "the truth" do
  #   assert true
  # end
  
  def setup
    ActionMailer::Base.deliveries.clear                                         # Mailerファイルを初期化しユーザーをセットアップ
  end
  
  test "invalid signup information" do                                          # 新規登録が失敗（フォーム送信が）した時用のテスト
    get signup_path                                                             # ユーザー登録ページにアクセス
    assert_no_difference 'User.count' do                                        # User.countでユーザー数が変わっていなければ（ユーザー生成失敗）true,変わっていればfalse
    post signup_path, params: { user: {    name: "",                             # signup_pathからusers_pathに対してpostリクエスト送信(/usersへ)、paramsでuserハッシュとその下のハッシュで値を受け取れるか確認
                                          email: "user@invalid",
                                          password:              "foo",
                                          password_confirmation: "bar" } }
    end
  assert_template 'users/new'                                                   # newアクションが描画(つまり@user.save失敗)されていればtrue、なければfalse
  assert_select   'div#error_explanation'                                       # divタグの中のid error_explanationが描画されていれば成功
  assert_select   'div.field_with_errors'                                       # divタグの中のclass field_with_errorsが描画されていれば成功
  assert_select   'form[action="/signup"]'                                      # formタグの中に`/signup`があれば成功
  
  end
  
  test "valid signup information with account activation" do                    # 新規登録が成功（フォーム送信）したかのテスト
    get signup_path                                                             # signup_path(/signup)ユーザー登録ページにアクセス
    assert_difference 'User.count', 1 do                                        # User.countでユーザー数をカウント、1とし、ユーザー数が変わったらtrue、変わってなければfalse
      post users_path, params: { user: { name:                 "Example User",  # signup_path(/signup)からusers_path(/users)へparamsハッシュのuserハッシュの値を送れるか検証
                                        email:                 "user@example.com",
                                        password:              "password",
                                        password_confirmation: "password" } }
    end
    assert_equal 1, ActionMailer::Base.deliveries.size                          # Actionメイラーが1かどうか検証
    user = assigns(:user)                                                       # usersコントローラの@userにアクセスし、userに代入
    assert_not user.activated?                                                  # userが有効化されていればfalse、されていなければtrue
    # 有効化していない状態でログインしてみる 
    log_in_as(user)                                                             # 有効化されていないuserでログイン
    assert_not is_logged_in?                                                    # 有効化されていなければtrue
    # 有効化トークンが不正な場合
    get edit_account_activation_path("invalid token", email: user.email)
    assert_not is_logged_in?
    # トークンは正しいがメールアドレスが無効な場合
    get edit_account_activation_path(user.activation_token, email: 'wrong')
    assert_not is_logged_in?
    # 有効化トークンが正しい場合
    get edit_account_activation_path(user.activation_token, email: user.email)
    assert user.reload.activated?
    follow_redirect!                                                            # 指定されたリダイレクト先(users/show)へ飛べるか検証
    assert_template 'users/show'                                                # users/showが描画されているか確認
    assert is_logged_in?                                                        # 新規登録時にセッションが空じゃなければtrue
  end

end
```

このテストで重要な行はこれ

```ruby
assert_equal 1, ActionMailer::Base.deliveries.size
```

このコードは、配信されたメッセージがきっかり1つであるかどうかを確認している。

配列`deliveries`は変数なので、setupメソッドでこれを初期化しておかないと`deliveries.clear`、並行して行われる他のテストでメールが配信されたときにエラーが発生してしまう。

また、`assigns`メソッドを使うと対応するアクション内のインスタンス変数にアクセスできるようになる。

例えば、Usersコントローラの`create`アクションでは`@user`というインスタンス変数が定義されているが、テストで`assings(:user)`と書くとこのインスタンス変数にアクセスできるようになる。

これでテストはパスする。

```ruby
$ rails t
2 tests, 14 assertions, 0 failures, 0 errors, 0 skips
```

テストができたので、ユーザー操作の一部をコントローラからモデルに移動するというリファクタリング行う準備ができた。

Userモデルでは、`activate`メソッドを作成してユーザーの有効化属性を更新し、`send_activation_email`メソッドを作成して有効化メールを送信する。

```rb:user.rb
  # アカウントを有効にする
  def activate
    update_attribute(:activated,        true)
    update_attribute(:activated_at,     Time.zone.now)
  end
  
  # 有効化用のメールを送信する
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end
  
private

  # メールアドレスを全て小文字にする

  def downcase_email
    self.email = email.downcase                                                 # emailを小文字化してUserオブジェクトのemail属性に代入
  end
  
  # 有効化トークンとダイジェストを作成および代入する
    
  def create_activation_digest
    self.activation_token   =   User.new_token                                  # ハッシュ化した記憶トークンを有効化トークン属性に代入
    self.activation_digest  =   User.digest(activation_token)                   # 有効化トークンをBcryptで暗号化し、有効化ダイジェスト属性に代入
  end
```

```rb:users_controller.rb
  def create
    @user = User.new(user_params)                                               # newビューにて送ったformの中身(nameやemailの値)をuser_paramsで受け取り、ユーザーオブジェクトを生成、@userに代入
    if @user.save
      @user.send_activation_email                                               # アカウント有効化メールの送信
      flash[:info] = "メールを確認してアカウントを有効化してね"                 # アカウント有効化メッセージの表示
      redirect_to root_url                                                      # ホームへ飛ばす
    else
      render 'new'
    end
  end
```

```rb:account_activations_controller.rb
class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.activate
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
end
```

`user.rb`では`user.`という記法を使っていない点に注目。
**Userモデルにはそのような変数はない**ので、これがあるとエラーになる。

```ruby
-user.update_attribute(:activated,    true)
-user.update_attribute(:activated_at, Time.zone.now)
+update_attribute(:activated,    true)
+update_attribute(:activated_at, Time.zone.now)
```

userをselfに切り替える方法もあるが、selfは必ずしも必須ではない。

これで、リファクタリングを施したテストがパスすればOK。

```ruby
2 tests, 14 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:activateメソッドはupdate_attributeを二回呼び出しているが、
これは各行で１回ずつDBへ問い合わせしていることになる。

`update_attribute`の呼び出しを1回の`update_columns`というメソッドでまとめてみる。

また、変更後テストがパスするかも確認。

```rb:user.rb
  # アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end
```

```ruby
11 tests, 15 assertions, 0 failures, 0 errors, 0 skips
```

2:現在は`/users`のユーザーindexページを開くと全てのユーザーが表示され、`/users/:id`のようにidと指定すると個別のユーザーを表示できる。

しかし、非有効ユーザーは表示する意味がないので、その動作を`users_controller.rb`で変更する。

```rb:users_controller.rb
  def index
    @users = User.where(activated: true).paginate(page: params[:page])          # Userを取り出して分割した値を@usersに代入
  end

  def show
    @user = User.find(params[:id])                                              # paramsで:idパラメータを受け取る(/users/1にアクセスしたら1を受け取る)
    redirect_to root_url and return unless @user.activated?                     # activatedがfalseならルートURLヘリダイレクト
  end
```

3:ここまでの演習問題で変更したコードをテストするために、/usersと/users/:idの両方に対する統合テストを作成する。

fixtureに非有効化ユーザーを作成

```yml:users.yml
non_activated:
 name: Non Activated
 email: non_activated@example.gov
 password_digest: <%= User.digest('password') %>
 activated: false
 activated_at: <%= Time.zone.now %>
```

setupでテストユーザーを読み込む

```rb:users_controller_test.rb
  test "should not allow the not activated attribute" do
    log_in_as (@non_activated_user)                                             # 非有効化ユーザーでログイン
    assert_not @non_activated_user.activated?                                   # 有効化でないことを検証
    get users_path                                                              # /usersを取得
    assert_select "a[href=?]", user_path(@non_activated_user), count: 0         # 非有効化ユーザーが表示されていないことを確認
    get user_path(@non_activated_user)                                          # 非有効化ユーザーidのページを取得
    assert_redirected_to root_url                                               # ルートurlにリダイレクトされればtrue
  end
```

##11.4 本番環境でのメール送信

ここまでの実装で、`development`環境に置けるアカウント有効化の流れは完成した。

次はサンプルアプリケーションの設定を変更し、production(実行)環境で実際にメールを送信してみる。

具体的には、

①無料のサービス(SendGrid)を利用してメール送信の設定をする
②アプリケーションの設定
③デプロイ

という順に行う。

####SendGridの使い方

本番環境からメールを送信するために、SendGridというHerokuアドオンを利用してアカウントを検証する。

チュートリアルでは、`starter tier`というサービスを使う。
(1日のメール数が最大400通という制限があるが、無料で利用できる)

SendGridをHerokuで利用するためには、クレジットカードの登録が必要。
https://heroku.com/verify
にアクセスし、クレジットカードを登録する。


アドオンをアプリケーションに追加するには

```
$ heroku addons:create sendgrid:starter
Creating sendgrid:starter on ⬢ yuuki-heroku-sample... free
Created sendgrid-rectangular-51178 as SENDGRID_PASSWORD, SENDGRID_USERNAME
Use heroku addons:docs sendgrid to view documentation
```

次に、SendGridアドオンの設定を行う。

production環境の**SMTP**に情報を記入する
本番Webサイトのアドレスをhost変数に定義する必要もある。

```rb:production.rb
  # Ignore bad email addresses and do not raise email delivery errors.
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
  config.action_mailer.raise_delivery_errors = false
  config.action_mailer.delivery_method = :smtp
  host = 'https://yuuki-heroku-sample.herokuapp.com'
  config.action.mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :address        => 'smtp.sendgrid.net',
    :port           => '587',
    :authentication => :plain,
    :user_name      => ENV['SENDGRID_USERNAME'],
    :password       => ENV['SENDGRID_PASSWORD'],
    :domain         => 'heroku.com',
    :enable_starttle_auto => true
  }

```

ここで重要なのが、
**`user_name`と`password`のハッシュに実際の値を記入しないこと**

ソースコードに直接機密情報を書き込むのは危険。
そのような情報は環境変数に記述し、そこからアプリケーションに読み込む必要がある。

今回の場合、そうした変数はSendGridアドオンが自動的に設定してくれるが、のちに環境変数を自分で設定しなければならない。

のちに扱うHerokuの環境変数を表示したい場合は、次のコマンドを実行する。

```ruby
$ heroku config:get SENDGRID_USERNAME
〇〇@heroku.com
$ heroku config:get SENDGRID_PASSWORD
〇〇
```

この時点で、トピックブランチをmasterにマージしておく

```ruby
$ git add -A
$ git commit -m "Add account activation"
$ git checkout master
$ git merge account-activation
```

続いて、リモートリポジトリにプッシュし、herokuにデプロイ。

```ruby
$ rails t
$ git push
$ git push heroku
$ heroku run rails db:migrate
```

ここで、デプロイ後のページで有効かメールを送信してみる。


しかし、なかなかメールが届かない・・・。

*SendGridを確認したところ、スパムアカウント扱いされてアカウントが停止されてしまいました。

![スクリーンショット 2019-01-26 22.11.15.png](https://qiita-image-store.s3.amazonaws.com/0/244639/12c812b0-e43f-4a12-d7da-2e7a808a22a5.png)

処理済みとなっていますが、本来なら送信済みになる筈です。

仕方なく、heroku logsからアカウント有効化URLをクリック。


![スクリーンショット 2019-01-26 22.43.03.png](https://qiita-image-store.s3.amazonaws.com/0/244639/eb54afcf-b631-0e78-6ffa-3ba1002e5bd6.png)

URLをクリックする。

![スクリーンショット 2019-01-26 22.43.40.png](https://qiita-image-store.s3.amazonaws.com/0/244639/69121d90-aaff-0b35-ee9d-1251c4e6840b.png)

有効化完了。

SendGridはすぐにスパム判定をしてしまうようで、自分と同じTutorialを読んでいる方もこの部分で苦戦している様子。

####演習

1：本番環境でメールを送る。

確認済み

2:実査にメールをクリックして有効化したあと、heorku logsから有効化に関するログがどうなっているかを調べる

```ruby
2019-01-26T13:43:36.501607+00:00 heroku[router]: at=info method=GET path="/account_activations/BLndwjAJEDxPtb90w3aNpQ/edit?email,yuukitetsuyanet%40gmail.com" host=yuuki-heroku-sample.herokuapp.com request_id=1129f9d8-c64a-476e-81b2-2d3d9a1fceff fwd="122.50.45.13" dyno=web.1 connect=1ms service=113ms status=302 bytes=1031 protocol=https
2019-01-26T13:43:36.712279+00:00 heroku[router]: at=info method=GET path="/users/2" host=yuuki-heroku-sample.herokuapp.com request_id=4f7857c1-a5ba-485f-abe6-feccfa902f9e fwd="122.50.45.13" dyno=web.1 connect=1ms service=19ms status=200 bytes=4444 protocol=https
```

一応、`heroku run rails c`でユーザーYUUKIのactivationがtrueか確認してみる。

```ruby
$ heroku run rails c
$ user = User.find(2)
$ user.activated
=> true
```

OK。

[第12章へ](https://qiita.com/yuuki_netb/items/04f3b87aac87ee359ed5)


#単語集

- バインド

関連付けるというIT用語。

- before_create

オブジェクト生成時のみ適用させるコールバック。

- Action Mailer

ActionMailerを使用することで、アプリケーションのメイラークラスやビューで、メールを送信することができる。
ActionMailerは`ActionMailer::Base`を継承し、`app/mailer`に配置され、app/views にあるビューと関連付けられる。

具体的な使い方は、まずメールを送信する為のメイラーとビューを生成し、メイラー内のアクションでメール送信の動きを付け、メイラービューでメールとして送られる中身のテンプレートを作成する。

- クエリパラメータ

URLの末尾で指定される?の部分。?の部分の後にパラメータを埋め込む。
実際のパラメータ値を「クエリ文字列」と呼び、その種類にはアクセス解析や広告からの流入を調べるパシッブパラエータと、実際のページのコンテンツの内容を変更させるアクティブパラメータがある

- assert_match

第二引数が第一引数の正規表現にマッチする場合はtrue

使い方

```ruby
assert_match (正規表現, string,[msg] )
```

- メタプログラミング

プログラムでプログラムを作成するというもの。Rubyでは様々なメソッドで利用可能だが、この章では`send`メソッドを用いてメタプログラミングを行う

- send

動的にメソッドを呼べるメソッド。

- update_columns

`update_attribute`メソッドの呼び出しを1行でまとめ、DBへの問い合わせを一回にまとめることができるメソッド。