Railsチュートリアル 12章まとめ

#Railsチュートリアル 12章まとめ
この章でやること

+ よくあるパスワードを忘れた際にパスワードの再設定をできるようにする
+ パスワードリセットコントローラーを作成する
+ メールを送信できるようにする
+ パスワードを再設定できるようにする

##12.1 PasswordResetsリソース
PasswordResetsリソースのモデリングから
必要なデータ（再設定用のダイジェストなど）をUserモデルに追加していく
	
PasswordResetsもリソースとして扱っていきたいので、まずは標準的なRESTfulなURLを用意。
	
###12.1.1 PasswordResetsコントローラ
コントローラーを生成　newとeditのメソッドも追加

```
$ rails generate controller PasswordResets new edit --no-test-framework
```
	
```no-test-framework``` はテストを生成しないオプション
今回は単体テストをせず統合テストでカバーするため
	
	
リスト 12.1: パスワード再設定用リソースを追加する

```config/routes.rb
	Rails.application.routes.draw do
	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'
	get    '/login',   to: 'sessions#new'
	post   '/login',   to: 'sessions#create'
	delete '/logout',  to: 'sessions#destroy'
	resources :users
	resources :account_activations, only: [:edit]
	resources :password_resets,     only: [:new, :create, :edit, :update]　#対応するビューを作るため
	end
```	

RESTfulのルーティング↓



| HTTPリクエスト | URL | Action |名前付きルート |
|:-----------|------------:|:------------:|:------------:|
| GET	| password_resets/new |	new	| new_password_reset_path |
|POST	|password_resets    |create	|password_resets_path
|GET	|password_resets/トークン/edit| edit|edit_password_reset_url(token)
|PATCH	|password_resets/トークン	|  update|  password_reset_url(token)
	
	
パスワード再設定画面へのリンクを追加する

```app/views/sessions/new.html.erb
	<% provide(:title, "Log in") %>
	<h1>Log in</h1>
	
	<div class="row">
	<div class="col-md-6 col-md-offset-3">
		<%= form_with(url: login_path, scope: :session, local: true) do |f| %>
	
		<%= f.label :email %>
		<%= f.email_field :email, class: 'form-control' %>
	
		<%= f.label :password %>
		<%= link_to "(forgot password)", new_password_reset_path %>  #パスワード忘れた時のリンク作成→createアクションへ
		<%= f.password_field :password, class: 'form-control' %>
	
		<%= f.label :remember_me, class: "checkbox inline" do %>
			<%= f.check_box :remember_me %>
			<span>Remember me on this computer</span>
		<% end %>
	
		<%= f.submit "Log in", class: "btn btn-primary" %>
		<% end %>
	
		<p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>
```

###12.1.2 新しいパスワードの設定
+ Userモデルに2つのカラムを追加していく
 
	・reset_digest属性　　　ハッシュ化した認証トークンを保存
	・reset_sent_at属性　　　再設定のリンクに有効期限をつけるためにリンク送信時間を記憶
	
migrationファイルを作成

```
$ rails generate migration add_reset_to_users reset_digest:string \ reset_sent_at:datetime
```
	
```
$ rails db:migrate
```
	
パスワード再設定用のメールアドレスを入力するビュー`password_resets/new.html.erb`を
`sessions/new.html.erb`を参考に作成
	
新しいパスワード再設定画面ビュー

```app/views/password_resets/new.html.erb
	<% provide(:title, "Forgot password") %>
	<h1>Forgot password</h1>
	
	<div class="row">
	<div class="col-md-6 col-md-offset-3">
		<%= form_with(url: password_resets_path, scope: :password_reset,  #createアクションへpath
						local: true) do |f| %>    
		<%= f.label :email %>
		<%= f.email_field :email, class: 'form-control' %>
	
		<%= f.submit "Submit", class: "btn btn-primary" %>
		<% end %>
	</div>
	</div>
```

###12.1.3 createアクションでパスワード再設定

以下機能を持ったcreateアクションを定義する

+ フォームから送信を行った後、メールアドレスをキーとしてユーザーをデータベースから見つける
+ パスワード再設定用トークンと送信時のタイムスタンプでデータベースの属性を更新する
+ ルートURLにリダイレクトし、フラッシュメッセージをユーザーに表示
+ 送信が無効の場合は、ログイン同様にnewページを出力してflash.nowメッセージを表示
	
	
パスワード再設定用のcreateアクション

```app/controllers/password_resets_controller.rb
	class PasswordResetsController < ApplicationController
	
	def create
		@user = User.find_by(email: params[:password_reset][:email].downcase)　#formで送信された小文字化したemailから@userを取得
		if @user           #もしuserが存在すれば
		@user.create_reset_digest       #@userのcreate_reset..メソッドを発動→パスワード再設定の属性を設定
		@user.send_password_reset_email		#パスワード再設定のメールを送信する
		flash[:info] = "Email sent with password reset instructions"
		redirect_to root_url             #root_urlに飛ばす
		else
		flash.now[:danger] = "Email address not found"   #もしuserがなければ、メール見つからないとメッセージ表示
		render 'new'
		end
	end
	
```
	
Userモデルにパスワード再設定用メソッドを追加する

```app/models/user.rb
	class User < ApplicationRecord
	attr_accessor :remember_token, :activation_token, :reset_token		#reset_token属性追加
	before_save   :downcase_email
	before_create :create_activation_digest
	.
	.
	.
	# アカウントを有効にする
	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
	
	# パスワード再設定の属性を設定する
	def create_reset_digest
		self.reset_token = User.new_token				　#トークンを作成しreset_token属性に代入
		update_attribute(:reset_digest,  User.digest(reset_token))  #Userモデルのreset_digestにresetトークンを入れてupdateする
		update_attribute(:reset_sent_at, Time.zone.now)  #Userモデルのreset_sent_atに現在時刻を入れてupdate
	end
	
	# パスワード再設定のメールを送信する
	def send_password_reset_email
		UserMailer.password_reset(self).deliver_now		#今すぐにメールを送信する
	end
	
	private
	
		# メールアドレスをすべて小文字にする
		def downcase_email
		self.email = email.downcase
		end
	
		# 有効化トークンとダイジェストを作成および代入する
		def create_activation_digest
		self.activation_token  = User.new_token
		self.activation_digest = User.digest(activation_token)
		end
	end
```	
	
###12.2 パスワード再設定のメール送信
・メールを送信できるようにする
	
###12.2.1 パスワード再設定のメールとテンプレート

```
UserMailer.password_reset(self).deliver_now
```

このコードを実装するためにUserメイラーにpassword_resetメソッドを作成し、テキストメールのテンプレートを変えていく
	
	
パスワード再設定のリンクをメール送信する

```app/mailers/user_mailer.rb
	class UserMailer < ApplicationMailer
	

	def password_reset(user)
		@user = user
		mail to: user.email, subject: "Password reset"
	end
	end
```

パスワード再設定のテンプレート（テキスト）

```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 two hours.
	
	If you did not request your password to be reset, please ignore this email and
	your password will stay as it is.

```

パスワード再設定のテンプレート（HTML）

```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>
	
```
パスワード再設定のプレビューメソッド（完成）

```test/mailers/previews/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    #password_resetメソッド定義
		user = User.first
		user.reset_token = User.new_token
		UserMailer.password_reset(user)
	end
	end
```
	
これでhttp://localhost:3000/rails/mailers/user_mailer/password_resetのURL
（localhost:3000の部分はAWSではサーバー名に書き換える.....com/rails/mailers/...）
	
###12.2.2 送信メールのテスト

メイラーメソッドのテストを書いていく
	
パスワード再設定用メイラーメソッドのテストを追加する

```test/mailers/user_mailer_test.rb
	require 'test_helper'
	
	test "password_reset" do
		user = users(:michael)
		user.reset_token = User.new_token		#userにreset_tokenを代入する
		mail = UserMailer.password_reset(user)		#mail変数にメールを送る
		assert_equal "Password reset", mail.subject  #メールのタイトルは"Password reset"になっているか
		assert_equal [user.email], mail.to          #メールの宛先はuser.emailになっているか
		assert_equal ["noreply@example.com"], mail.from     #差出人はexample.comになっているか
		assert_match user.reset_token,        mail.body.encoded   #reset_tokenが入っているか
		assert_match CGI.escape(user.email),  mail.body.encoded   #userの.emailはescapeされているか@が%になっているか
	end
	end
	
```

###12.3 パスワードを再設定する
次はPasswordResetsコントローラのeditアクションの実装
	
##12.3.1 editアクションで再設定
先ほど定義したパスワード再設定の送信メールには、次のようなリンクが含まれている
https://example.com/password_resets/3BdBrXeQZSWqFIDRN8cxHA/edit?email=fu%40bar.com
	
このリンクを機能させるには、リンク先のパスワード再設定フォームを表示するビューを作る
	
注意点
メアドをキーにuserを検索するにはeditアクションとupdateアクションの両方でメアドが必要になる
フォームを一度送信してしまうと、メアドの情報は消えてしまう。
今回はこのメールアドレスを保持するため、隠しフィールドとしてページ内に保存する手法をとる
これにより、フォームから送信したときに、他の情報と一緒にメールアドレスが送信されるようになる
	
パスワード再設定のフォーム

```app/views/password_resets/edit.html.erb
	<% provide(:title, 'Reset password') %>
	<h1>Reset password</h1>
	
	<div class="row">
	<div class="col-md-6 col-md-offset-3">
		<%= form_with(model: @user, url: password_reset_path(params[:id]),
					local: true) do |f| %>
		<%= render 'shared/error_messages' %>
	
		<%= hidden_field_tag :email, @user.email %>　　　＃hidden_field_tagを追加しメールも送るようにする
	
		<%= f.label :password %>                   #password入力フォーム
		<%= f.password_field :password, class: 'form-control' %>
	
		<%= f.label :password_confirmation, "Confirmation" %>		#確認用password入力フォーム
		<%= f.password_field :password_confirmation, class: 'form-control' %>
	
		<%= f.submit "Update password", class: "btn btn-primary" %>
		<% end %>
	</div>
	</div>
```


`hidden_field_tag :email, @user.email`　はメールアドレスがparams[:email]に保存される
	`f.hidden_field :email, @user.email`　はparams[:user][:email]に保存される
	
次はeditアクションにparams[:email]が入った@userインスタンス変数を定義
authenticated?メソッドを使って認証する
	
	
パスワード再設定のeditアクション

```app/controllers/password_resets_controller.rb
	class PasswordResetsController < ApplicationController
	before_action :get_user,   only: [:edit, :update]
	before_action :valid_user, only: [:edit, :update]#正しいユーザーだけ
	.
	private
	
		def get_user
		@user = User.find_by(email: params[:email])		#@userインスタンス変数＝リンクで受け取ったメアドでuserを探す
		end
	
		# 正しいユーザーかどうか確認する
		def valid_user
		unless (@user && @user.activated? &&　			#userが存在し、activated?がtrueかつ
				@user.authenticated?(:reset, params[:id]))		#reset_digest(テーブルのデータ)がリンクのURLと一致しているか
			redirect_to root_url
		end
		end
	end
```
抽象化したauthenticated?はsample_appで以下のように使われた

```
	authenticated?(:reset, params[:id])
	authenticated?(:remember, cookies[:remember_token])
	authenticated?(:activation, params[:id])
```

###12.3.2 パスワードを更新する
AccountActivationsコントローラのeditアクションでは、ユーザーの有効化ステータスをfalseからtrueに変更したが、
今回の場合はフォームから新しいパスワードを送信するようになる。
したがって、フォームからの送信に対応するupdateアクションが必要
このupdateアクションでは、次の４つのケースを考慮する必要がある。

1. パスワード再設定の有効期限が切れていないか
1. 無効なパスワードであれば失敗させる（失敗した理由も表示する）
1. 新しいパスワードが空文字列になっていないか（ユーザー情報の編集ではOKだった）
1. 新しいパスワードが正しければ、更新する

（1）と（2）と（4）はこれまでの知識で対応できそうだが、（3）はどのように対応すれば良いのかあまり明確ではない

（1）「パスワード再設定の有効期限が切れていないか」については、editとupdateアクションに次のようなメソッドとbeforeフィルターを用意することで対応できそう

```
before_action :check_expiration, only: [:edit, :update]    # （1）への対応案
```

このcheck_expirationメソッドは、有効期限をチェックするPrivateメソッドとして定義

```
# 期限切れかどうかを確認する
def check_expiration
  if @user.password_reset_expired?#有効期限が切れていたら
    flash[:danger] = "Password reset has expired."
    redirect_to new_password_reset_url
  end
end
```

期限切れかどうかを確認するインスタンスメソッド「password_reset_expired?」を使っている。

beforeフィルターで保護したupdateアクションを使うことで、（2）「無効なパスワードであれば失敗させる（失敗した理由も表示する）」と（4）「新しいパスワードが正しければ、更新する」のケースに対応することができそう
例えば（2）については、更新が失敗したときにeditのビューが再描画され、パーシャルにエラーメッセージが表示されるようにすれば解決できる。
（4）については、更新が成功したときにパスワードを再設定し、あとはログインに成功したときと同様の処理を進めていけば問題なさそう。

ちょっと厄介なのが、パスワードが空文字だった場合の処理。
以前Userモデルを作っていたときに、パスワードが空でも良いallow_nilを実装をしたから
したがって、このケースについては明示的にキャッチするコードを追加する必要がある
これが考慮すべき点の（3）「 新しいパスワードが空文字列になっていないか（ユーザー情報の編集ではOKだった）」に当たる。
解決する方法として、今回は@userオブジェクトにエラーメッセージを追加する方法をとってみる

次のように`errors.add`を使ってエラーメッセージを追加する。

```
@user.errors.add(:password, :blank)
```

このように書くと、パスワードが空だった時に空の文字列に対するデフォルトのメッセージを表示してくれるようになる

以上の結果をまとめると、（1）のpassword_reset_expired?の実装を除き、すべてのケースに対応したupdateアクションが完成


```app/controllers/password_resets_controller.rb
class PasswordResetsController < ApplicationController
  before_action :get_user,         only: [:edit, :update]
  before_action :valid_user,       only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update]    # （1）への対応

  def new
  end

  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

  def edit
  end

  def update
    if params[:user][:password].empty?                  # （3）への対応
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)                     # （4）への対応
      log_in @user
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'                                     # （2）への対応
    end
  end

  private

    def user_params
      params.require(:user).permit(:password, :password_confirmation)
    end

    # beforeフィルタ

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 有効なユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end

    # トークンが期限切れかどうか確認する
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end
end
```


あとは、残しておいたpassword_reset_expired?の実装だけ

```
@user.password_reset_expired?
```

上のコードを動作させるために、password_reset_expired?メソッドをUserモデルで定義していく
このメソッドではパスワード再設定の期限を設定して、2時間以上パスワードが再設定されなかった場合は期限切れとする処理を行う

```
reset_sent_at < 2.hours.ago
```

上の` < `記号を「〜より少ない」と読んでしまうと、「パスワード再設定メール送信時から経過した時間が、2時間より少ない場合」となってしまうので、「少ない」ではなく「〜より早い時刻」と読む。
こうすると「パスワード再設定メールの送信時刻が、現在時刻より2時間以上前（早い）の場合」となり、 期待どおりの条件となる

```app/models/user.rb
class User < ApplicationRecord

  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end
```

これでpassword_resets_controller.rbのcreateアクションが動く


###12.3.3 パスワードの再設定をテストする

送信に成功した場合と失敗した場合の統合テストを作成する

まずは統合テストを作成

```
$ rails generate integration_test password_resets
      invoke  test_unit
      create    test/integration/password_resets_test.rb
```

パスワード再設定をテストする手順は、

1. 最初に「forgot password」フォームを表示して無効なメールアドレスを送信
1. 次はそのフォームで有効なメールアドレスを送信
1. パスワード再設定用トークンが作成され、再設定用メールが送信される。
1. メールのリンクを開いて無効な情報を送信
1. そのリンクから有効な情報を送信して、それぞれが期待どおりに動作することを確認

```test/integration/password_resets_test.rb
require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear
    @user = users(:michael)
  end

 test "password resets" do
    get new_password_reset_path#forgot passのリンク取得
    assert_template 'password_resets/new'#passリセット画面出るか
    assert_select 'input[name=?]', 'password_reset[email]'#input[name]がpassword_resetにあるか
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?#flashが空でないか
    assert_template 'password_resets/new'#newの画面を再描写するか
    # メールアドレスが有効
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    assert_not_equal @user.reset_digest, @user.reload.reset_digest#reset_digestカラムがreload後変化ないか
    assert_equal 1, ActionMailer::Base.deliveries.size #mailは1通だけ送ったか
    assert_not flash.empty?#flashが空でないか
    assert_redirected_to root_url#rootへリダイレクトしたか
    # パスワード再設定フォームのテスト
    user = assigns(:user)
    # メールアドレスが無効
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # 無効なユーザー
    user.toggle!(:activated)#activatedカラムをtrueかfalseに反転（この場合false）
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url#失敗することを確認
    user.toggle!(:activated)#trueに戻す
    # メールアドレスが有効で、トークンが無効
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # メールアドレスもトークンも有効
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_template 'password_resets/edit'#editページが描写されるか
    assert_select "input[name=email][type=hidden][value=?]", user.email#inputタグに正しい名前、type="hidden"、メールアドレスがあるかどうかを確認
    # 無効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),#createアクションへ送信
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "barquux" } }
    assert_select 'div#error_explanation'#errorのdivタグ出るか確認
    # パスワードが空
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "",
                            password_confirmation: "" } }
    assert_select 'div#error_explanation'#errorのdivタグ出るか確認
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert is_logged_in?#ログインされたか
    assert_not flash.empty?#flashは空でないか
    assert_redirected_to user#@userページへ飛ぶか
  end
end
```

今回の新しい要素はinputタグぐらい

```Ruby
assert_select "input[name=email][type=hidden][value=?]", user.email
```

上のコードは、inputタグに正しい名前、type="hidden"、メールアドレスがあるかどうかを確認している

```html
<input id="email" name="email" type="hidden" value="michael@example.com" />
```

テストコードは green になるはず

###演習
1リスト 12.6にあるcreate_reset_digestメソッドはupdate_attributeを２回呼び出していますが、これは各行で１回ずつデータベースへ問い合わせしていることになります。リスト 12.20に記したテンプレートを使って、update_attributeの呼び出しを１回のupdate_columns呼び出しにまとめてみましょう（これでデータベースへの問い合わせが１回で済むようになります）。また、変更後にテストを実行し、 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
```

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

```password_reset_test.rb
test "expired token" do
    get new_password_reset_path#reset_pathを取得
    post password_resets_path,#mailを送る
         params: { password_reset: { email: @user.email } }

    @user = assigns(:user)
    @user.update_attribute(:reset_sent_at, 3.hours.ago)#メールを送った時間を3時間前にセット
    patch password_reset_path(@user.reset_token),#passwordの再設定をする
          params: { email: @user.email,
                    user: { password:              "foobar",
                            password_confirmation: "foobar" } }
    assert_response :redirect
    follow_redirect!
    assert_match /expired/i, response.body#Html bodyタグにexpiredがあるか確認
  end
```

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

```app/controllers/password_resets_controller.rb

  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)
      log_in @user
      @user.update_attribute(:reset_digest, nil)#追加するだけ
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end
```

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

```
test "password resets" do
....
patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert_nil user.reload.reset_digest
    assert is_logged_in?#ログインされたか
    assert_not flash.empty?#flashは空でないか
    assert_redirected_to user#@userページへ飛ぶか
```

一番最後に追加するとエラーになるので、注意

###12.4 本番環境でのメール送信（再掲）
これでパスワード再設定の実装も終わった。
あとはproduction環境でも動くようにするだけ

前章でやっているので、細かな設定はスキップ

以下スキップOK

本番環境からメール送信するために、「Mailgun」というHerokuアドオンを利用してアカウントを検証します
本チュートリアルでは、「starter tier」というサービスを使う


アプリケーションでMailgunアドオンを使うには、production環境のSMTPに情報を記入する
本番Webサイトのアドレスをhost変数に定義する必要がある。<your heroku app>を自分のHerokuのURLに設定してください。その他の設定はこのまま使える


```config/environments/production.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  host = '<your heroku app>.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :port           => ENV['MAILGUN_SMTP_PORT'],
    :address        => ENV['MAILGUN_SMTP_SERVER'],
    :user_name      => ENV['MAILGUN_SMTP_LOGIN'],
    :password       => ENV['MAILGUN_SMTP_PASSWORD'],
    :domain         => host,
    :authentication => :plain,
  }
  .
  .
  .
end
```

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

``
$ rails test
$ git add -A
$ git commit -m "Add password reset"
$ git checkout master
$ git merge password-reset
```

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

```
$ rails test
$ git push && git push heroku
$ heroku run rails db:migrate
```

MailgunのHerokuアドオンをまだ追加していなければ、次のコマンドを実行

```
$ heroku addons:create mailgun:starter
```

注: herokuコマンドのバージョンが古いとここで失敗するかも。その場合はHeroku Toolbeltを使って最新版に更新するか、次の古い文法のコマンドを試す

```
$ heroku addons:add mailgun:starter
```

メール設定にはMailgunアカウントのuser_nameとpassword設定を記入する行もありますが、そこには記入せず、必ず環境変数「ENV」に設定するよう注意。
本番運用するアプリケーションでは、暗号化されていないIDやパスワードのような重要なセキュリティ情報は「絶対に」ソースコードに直接書き込まない。
そのような情報は環境変数に記述し、そこからアプリケーションに読み込む。
今回の場合、そうした変数はMailgunアドオンが自動的に設定してくれる

最後に、受信メールの認証を行います。以下のコマンドを打つと、Mailgun ダッシュボードのURLが表示されるのでブラウザで開きます。

```
$ heroku addons:open mailgun
```

MailGun公式ドキュメントに従い、受信するメールアドレスを認証。
画面左側の「Sending」→「Domains」のリストにある「sandbox」で始まるサンドボックスドメインを選択。
画面右側の「Authorized Recipients」から受信メールアドレスを認証し、本番環境でのメール送信準備は完了。

Herokuへのデプロイが完了したら、ログインページの［forgot password］リンクをクリックして、production環境でパスワードの再設定を行ってみる。
フォームから送信すると、メールが送信されてくるはず。
記載されているリンクをクリックし、無効なパスワードと有効なパスワードをそれぞれ試してみましょう。


###演習
production環境でユーザー登録を試してみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?
メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。
アカウントを有効化できたら、今度はパスワードの再設定を試してみましょう。正しくパスワードの再設定ができたでしょうか?

herokuエラー中なのでスキップ