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

#Railsチュートリアル 11章

##11章アカウントの有効化

###この章の趣旨
+ SMS認証的なものを作る
+ メールをユーザーに送ってユーザーがメールに記載されているURLをクリックすることで認証とする機能を追加する
+ 有効化トークン（文字列）を含めたリンクをメールで送信する
	
###11.1 AccountActivationsリソース
まずはUserモデルに必要なカラムを追加する


	
###11.1.1 AccountActivationsコントローラ

まずはAccountActivationsコントローラを生成。
``$ rails generate controller AccountActivations``
	
まずは、名前付きルートを扱えるように、ルーティングにアカウント有効化用のresources行を追加

```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]　#追加
	end
  ```

追加した行は```account_activation/トークン/edit```
のURLでルートを拾う
	

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

送信メールの有効化トークンとデータベースの有効化トークンを同じにすると、セキュリティが危ないので
なのでパスワード設定と同様に仮想的な属性を使ってハッシュ化した文字列をデータベースに保存する
	
仮想属性の有効化トークンは
```user.activation_token```
この値がuserと合っていれば認証OKとなる
	
ユーザーの認証は
```user.authenticated?(:activation, token)```
autenticated?メソッドを使ってメールについている有効化トークンと仮想属性の有効化トークンを比べる
	
続いて、activated属性を追加して論理値を取る
```if user.activated? ...```
既に有効化されているか、いないかチェックする　デフォルトはfalse状態

最後に　ユーザーを有効にしたときの日時も念のために記録
	
Userモデルに上記3つのカラムを追加するための
次のマイグレーションをコマンドラインで実行

``` console
rails generate migration add_activation_to_users \
activation_digest:string activated:boolean activated_at:datetime
```
```db/migrate/[timestamp]_add_activation_to_users.rb
	class AddActivationToUsers < ActiveRecord::Migration[6.0]
	def change
		add_column :users, :activation_digest, :string
		add_column :users, :activated, :boolean, default: false 　　#default設定する
		add_column :users, :activated_at, :datetime
	end
	end
```
マイグレーションを実行
```rails db:migrate
```
	
###Activationトークンのコールバック

Userモデルにアカウント有効化のコードを追加する

```app/models/user.rb
	class User < ApplicationRecord
	attr_accessor :remember_token, :activation_token             #activation_token属性を追加
	before_save   :downcase_email					    #saveの前にemail小文字化
	before_create :create_activation_digest				#ユーザが作成される前にcreate...を呼び出す
	validates :name,  presence: true, length: { maximum: 50 }
	.
	.
	.
	private     #privateより下に記述したメソッドは外部に公開されない　以下consoleで確認
	
		# メールアドレスをすべて小文字にする
		def downcase_email
		self.email = email.downcase
		end
	
		# 有効化トークンとダイジェストを作成および代入する
		def create_activation_digest
		self.activation_token  = User.new_token		#アクセサーで定義ずみ　認証用のtokenを代入　※認証と同じ文字列
		self.activation_digest = User.digest(activation_token)	#Userモデルのdigestカラムに上で定義した認証用トークンを代入
		end
	end
```
	
※クラス内でprivateキーワードより下に記述したメソッドは自動的に非公開となる


```
$ rails console
>>User.first.create_activation_digest
 NoMethodError: private method `create_activation_digest' called for #<User>

```

###サンプルユーザーの生成とテスト
 サンプルデータとfixtureも更新し、テスト時のサンプルとユーザーを事前に有効化しておく
※Time.zone.nowはRailsの組み込みヘルパーであり、サーバーのタイムゾーンに応じたタイムスタンプを返す
	
```db/seeds.rb
	# メインのサンプルユーザーを1人作成する
	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
```

fixtureのユーザーも同様に行う　（コード省略）

マイグレーションファイルも更新

```
$ rails db:migrate:reset
$ rails db:seed
```
	
###11.2 アカウント有効化のメール送信
+ メールのテンプレートを作成する
+ ビューと同様にメールをブラウザでプレビューできる
	
###11.2.1 送信メールのテンプレート
```rails generate``` でコントローラー同様にメイラーを作成する　※メイラーはメールを送るものです
	

```console.
$ rails generate mailer UserMailer account_activation password_reset
```
これで
account_activationメソッドと、password_resetメソッドが生成
さらにビューのテンプレートが2つずつ生成される
1つはテキストメール用のテンプレート、1つはHTMLメール用のテンプレート。
	
```app/mailers/application_mailer.rb
	class ApplicationMailer < ActionMailer::Base
	default from: "noreply@example.com"			#メールのfromをカスタマイズした
	layout 'mailer'
	end
```
```app/mailers/user_mailer.rb
	class UserMailer < ApplicationMailer
	
	def account_activation(user)
		@user = user					#@userインスタンス変数を作成
		mail to: user.email, subject: "Account activation"	#subject:はメールの件名
	end
	
	def password_reset
		@greeting = "Hi"
	        ail to: "to@example.org"
	end
	end
```

今時点はtestはred
メールのテンプレートにクリックしてもらうURLを追加する

```app/views/user_mailer/account_activation.text.erb
	Hi <%= @user.name %>,    #登録したユーザーの名前を表示
	
	Welcome to the Sample App! Click on the link below to activate your account:
	#サンプルアプリへようこそ！ 以下のリンクをクリックして、アカウントをアクティブ化してください。

	<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
	#editアクションへurlを送る　urlの内容はtokenとemail
```
HTMLビュー

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

```

####edit_account_activation_url(@user.activation_token,email:@user:email)について解説
	
edit_account_activation_urlは``routes``においた
 ``resources :account_activations, only: [:edit]``にリクエストを送信する
実際のURLはこんな感じになる
``https://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit``

引数の``@user.activation_token,email:@user:email``は
``user.rb``で定義したactivation_token　と　user.emailを指す

```app/models/user.rb
# 有効化トークンとダイジェストを作成および代入する
		def create_activation_digest
		self.activation_token  = User.new_token		#アクセサーで定義ずみ　認証用のtokenを代入　※認証と同じ文字列
		self.activation_digest = User.digest(activation_token)	#Userモデルのdigestカラムに上で定義した認証用トークンを代入
		end
```

つまりURLの末尾に .../認証用のtoken/user.emailという形になる
	
/users/1/editの「1」のようなユーザーIDと同じ役割を果たす
認証用のトークンは、AccountActivationsコントローラのeditアクションで,paramsハッシュでparams[:id]として参照可能なので
``edit_account_activation_url(@user.activation_token, email: @user.email)``で
認証トークンとメールアドレスを取得できる
　※参考　emailアドレスは@マークがついている→example@example.com 
　　　　　　@マークはURLでは利用できないので、（利用できない文字を回避することをエスケープという）
``edit_account_activation_url(@user.activation_token, email: @user.email)``のように
	名前付きルートでクエリパラメータを定義すると、Railsが特殊な文字を自動的にエスケープしてくれます。
コントローラでparams[:email]からメールアドレスを取り出すときには、自動的にエスケープを解除してくれます。
	
	→``account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com`` 
→@が%40になっている　　
													
	

###11.2.2 送信メールのプレビュー
特定のURLをクリックすることでメールプレビューが使える
利用するにはアプリケーションのdevelopment環境の設定に手を加える必要がある。
	
development環境のメール設定

```config/environments/development.rb
	Rails.application.configure do
	.
	.
	.
	config.action_mailer.raise_delivery_errors = false
	
	host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。
	# クラウドIDEの場合
	config.action_mailer.default_url_options = { host: host, protocol: 'https' }
	# localhostで開発している場合
	# config.action_mailer.default_url_options = { host: host, protocol: 'http' }
	.
	.
	.
	end
```
ホスト名 'example.com' の部分は、各自のdevelopment環境に合わせて変更する
	
host = '<hex string>.vfs.cloud9.us-east-2.amazonaws.com'     # クラウドIDE
	config.action_mailer.default_url_options = { host: host, protocol: 'https' }
	※'<hex string>はサーバーのURLを入れる

もしローカル環境で開発している場合
	host = 'localhost:3000'                     # ローカル環境
	config.action_mailer.default_url_options = { host: host, protocol: 'http' }
	特に2番目の例では、httpsが暗号化なしのhttpに変わっている
	
	
Userメイラーはプレビューファイルを自動生成しており、それのテンプレートを変更する
	
```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変数を定義　Userモデルの1番初め
		user.activation_token = User.new_token	#userのactivation_tokenカラムに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
```
指定のURLでメールをプレビューできる
	http://localhost:3000/rails/mailers/user_mailer/account_activation
	localhostの部分をhost=に代入したページを挿入する
	
###11.2.3 送信メールのテスト
railsによって自動でtestが作られているのでそれを利用してtestする

```test/mailers/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　#user.nameは入っているか
			assert_match user.activation_token,   mail.body.encoded　#認証トークンは入っているか
			assert_match CGI.escape(user.email),  mail.body.encoded　#@マークは%に変わってるか
		end
		end
```	
今時点はまだred
		
※assert_matchメソッド
→正規表現で文字列をテストできる　指定した文字列がは良いているか？チェックする
		
		assert_match 'foo', 'foobar'      # true
		assert_match 'baz', 'foobar'      # false
		assert_match /\w+/, 'foobar'      # true
		assert_match /\w+/, '$#!*+@'      # false
		
※CGI.escape(user.email)
テスト用のユーザーのメールアドレスをエスケープする
		
		
このテストがパスするには、テストファイル内のドメイン名を正しく設定する必要がある

```config/environments/test.rb
		Rails.application.configure do
		.
		.
		.
		config.action_mailer.delivery_method = :test
		config.action_mailer.default_url_options = { host: 'example.com' }
		.
		.
		.
		end
```
これでtestはpassする
		
		
###11.2.4 ユーザーのcreateアクションを更新
 + createアクションはuserが新規登録するアクション
 + ユーザが新規登録した後に、認証メールを送る機能を追加する
 
		
	↓変更前のcreateアクション

```app/controllers/users_controller.rb
	def create
			@user=User.new(user_params)
			if @user.save
			log_in @user	#log_inしてるか
			flash[:success] = "Welcome to the Sample App!"
			redirect_to @user　　#userのshowビューへ送る
			else
			render 'new'
			end
	end
```

変更後のcreateアクション

```app/controllers/users_controller.rb
		class UsersController < ApplicationController
		.
		.
		.
		def create
			@user = User.new(user_params)
			if @user.save
			UserMailer.account_activation(@user).deliver_now　　 
    #メールを送信する
    #deliver_nowは、今すぐに送信したい場合に使用
			flash[:info] = "Please check your email to activate your account."
			redirect_to root_url	#homeへ送るように
			else
			render 'new'
			end
		end
		.
		.
		.
		end
```

createの挙動を少し変えたためtestがredになった
合っていないtestは一旦コメントアウトし、後で戻す
		
		
```test/integration/users_signup_test.rb

		require 'test_helper'
		
		class UsersSignupTest < ActionDispatch::IntegrationTest
		
		test "invalid signup information" do
			get signup_path
			assert_no_difference 'User.count' do
			post users_path, params: { user: { name:  "",
												email: "user@invalid",
												password:              "foo",
												password_confirmation: "bar" } }
			end
			assert_template 'users/new'
			assert_select 'div#error_explanation'
			assert_select 'div.field_with_errors'
		end
		
		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 is_logged_in?
		end
		end
```
		
これで実際にはmail飛んでないが、serverで確認することができる
		
サーバーログ　こんな感じ
		UserMailer#account_activation: processed outbound mail in 5.1ms
		Delivered mail 5d606e97b7a44_28872b106582df988776a@ip-172-31-25-202.mail (3.2ms)
		Date: Fri, 23 Aug 2019 22:54:15 +0000
		From: noreply@example.com
		To: michael@michaelhartl.com
		Message-ID: <5d606e97b7a44_28872b106582df988776a@ip-172-31-25-202.mail>
		Subject: Account activation
		Mime-Version: 1.0
		Content-Type: multipart/alternative;
		boundary="--==_mimepart_5d606e97b6f16_28872b106582df98876dd";
		charset=UTF-8
		Content-Transfer-Encoding: 7bit
		
		
メールの内容もみれる
		----==_mimepart_5d606e97b6f16_28872b106582df98876dd
		Content-Type: text/plain;
		charset=UTF-8
		Content-Transfer-Encoding: 7bit
		
		Hi Michael Hartl,
		
		Welcome to the Sample App! Click on the link below to activate your account:
		
		https://0ebe1dc6d40e4a4bb06e0ca7fe138127.vfs.cloud9.us-east-2.
		amazonaws.com/account_activations/zdqs6sF7BMiDfXBaC7-6vA/
		edit?email=michael%40michaelhartl.com　
                ↑認証トークンとメールアドレスもしっかり入っている

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

+ AccountActivationsコントローラのeditアクションを書く
+ editアクションは認証メールをクリックした後の受け取り機能
+ テストも実施
+ リファクタリングも行う
	
###11.3.1 authenticated?メソッドの抽象化　

+ authnticatedメソッドをいろんなシーンで使えるようにする

最終的にautenticatedメソッドを以下のように書き換える

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

元々のautenticatedメソッド↓↓

```
# トークンがダイジェストと一致したらtrueを返す
		def authenticated?(remember_token)
			return false if remember_digest.nil?
			BCrypt::Password.new(remember_digest).is_password?(remember_token)
		end
```		
元々autenticatedメソッドはremember_tokenを引数にとり 
クッキーのセクションがUserテーブルのremember_digestカラムと一致すればtrueを返すメソッド

ここに引数を1つ加え、``digest = send("#{attribute}_digest")``という変数展開をして変更を加えている

まずは``send``について解説
sendメソッドは渡されたオブジェクトにメッセージを送ることにより、呼び出すメソッドを動的に決めれる
		
		$ rails console
		>> a = [1, 2, 3]
		>> a.length
		=> 3
		>> a.send(:length)
		=> 3
		>> a.send("length")
		=> 3

変数aに（引数のメソッド）を渡しているため　同じ結果になる
		
もう１つ例

		>> user = User.first
		>> user.activation_digest
		=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
		>> user.send(:activation_digest)
		=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
		>> user.send("activation_digest")
		=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
		>> attribute = :activation
		>> user.send("#{attribute}_digest")
		=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
		
sendの引数内で変数展開をしてメソッドを正しく認識できる
つまり
send(#{変数}_digest) は
↓↓
``#{attribute}_digest``
シンボルと文字列どちらを使った場合でも、上のコードは次のように文字列に変換されます。
``activation_digest`` 引数activatinとした場合

つまり
```def authenticated?(attribute, token)
			digest = send("#{attribute}_digest")
```
は引数``attribute``を利用して”変数_digest”→変数を変えれば
1. ``activation_digest``にも
2. ```password_digest```
3.  ```remember_digest```
にもなれる

このような受け取ったパラメータ変数によって呼び出すメソッドを変えることを「メタプログラミング」と呼ばれ
メタプログラミングとは「プログラムでプログラムを作成する」ことです
		
これでautenticated?メソッドは　メールの認証でも、クッキーの認証でも使えるようになった

次にすすむ前に、関連するcurrent_userメソッド （sessions_helper記載）とnilダイジェストのテスト（user_test.rb）の両方で、
	authenticated?が古く、引数も2つではなくまだ1つのままだから書き換えます
		
```app/helpers/sessions_helper.rb
		module SessionsHelper
		.
		.
		.
		# 現在ログイン中のユーザーを返す（いる場合）
		def current_user
			if (user_id = session[:user_id])
			@current_user ||= User.find_by(id: user_id)
			elsif (user_id = cookies.signed[:user_id])
			user = User.find_by(id: user_id)
			if user && user.authenticated?(:remember, cookies[:remember_token])　#引数2つ
				log_in user
				@current_user = user
			end
			end
		end
		.
		.
		.
		end
```

```test/models/user_test.rb
		require 'test_helper'
		
		class UserTest < ActiveSupport::TestCase
		
		def setup
			@user = User.new(name: "Example User", email: "user@example.com",
							password: "foobar", password_confirmation: "foobar")
		end
		.
		.
		.
		test "authenticated? should return false for a user with nil digest" do
			assert_not @user.authenticated?(:remember, '')　　#引数2つ
		end
		end
```
これでtestもpassして、autenticated?メソッドは抽象化できました
		
		
###11.3.2 editアクションで有効化

+ editアクションでユーザーの認証をする
+ メールで送った認証トークンと、Userテーブルのactivation_digestが同じであればログイン可能
+ 異なる場合はログインせずに、ルートに戻す処理を行う

editアクションで、paramsハッシュで渡されたメールアドレスに対応するユーザーを認証
	
```
if user && !user.activated? && user.authenticated?(:activation, params[:id])
	#もしuserが存在し、かつuserがactivatedがtrueかつactivation_digestがparams[:id](認証トークン)と一緒なら
```	
!user.activated?という記述は、既に有効になっているユーザーを誤って再度有効化しないために必要
	
上の論理値に基いてユーザーを認証するには、ユーザーを認証してからactivated_atタイムスタンプを更新する必要がある
	
```
user.update_attribute(:activated,    true)  
　#userテーブルのactivatedカラムをtrueにアップデートする
user.update_attribute(:activated_at, Time.zone.now)
　#userテーブルのactivated_atカラムを現在時間にアップデートする
```	
上のコードをeditアクションで使います。
	
```app/controllers/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] = "Account activated!"
		redirect_to user
		else　　　
		flash[:danger] = "Invalid activation link"
		redirect_to root_url
		end
	end
	end
```

```app/controllers/sessions_controller.rb

	class SessionsController < ApplicationController
	
	def new
	end
	
	def create
		user = User.find_by(email: params[:session][:email].downcase)
		if user && user.authenticate(params[:session][:password])
		if user.activated?
			log_in user
			params[:session][:remember_me] == '1' ? remember(user) : forget(user)
			redirect_back_or user
		else
　　　　　　　　　#トークンが無効になるようなことはめったにないが、もしそうなった場合はルートURLにリダイレクトされる仕組み
			message  = "Account not activated. "
			message += "Check your email for the activation link."
			flash[:warning] = message
			redirect_to root_url
		end
		else
		flash.now[:danger] = 'Invalid email/password combination'
		render 'new'
		end
	end
	
	def destroy
		log_out if logged_in?
		redirect_to root_url
	end
	end
```

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

+ アカウント認証の統合テストを追加する
+ 前に作ったユーザー登録のテストを修正する

ユーザー登録のテストにアカウント有効化を追加

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

class UsersSignupTest < ActionDispatch::IntegrationTest

  def setup
    ActionMailer::Base.deliveries.clear      #ハイライト
  end

  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post users_path, params: { user: { name:  "",
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
    end
    assert_template 'users/new'
    assert_select 'div#error_explanation'
    assert_select 'div.field_with_errors'
  end

  test "valid signup information with account activation" do　　
    get signup_path                 #新規登録
    assert_difference 'User.count', 1 do    #userのcountが1になったか
      post users_path, params: { user: { name:  "Example User",		#　paramsにユーザーデータin
                                         email: "user@example.com",
                                         password:              "password",
                                         password_confirmation: "password" } }
    end
    assert_equal 1, ActionMailer::Base.deliveries.size   #送ったメールが1通かどうか
    user = assigns(:user)         #assignsはインスタンス変数にアクセスできるようになる
    assert_not user.activated?     #userカラムのactivatedがfalseでないか
    # 有効化していない状態でログインしてみる    
    log_in_as(user)            #userでlog_inする
    assert_not is_logged_in?   #log_inしていないか確認
    # 有効化トークンが不正な場合
    get edit_account_activation_path("invalid token", email: user.email)  #おかしいurl　（トークン）を取得
    assert_not is_logged_in?                  #loginしていないか確認
    # トークンは正しいがメールアドレスが無効な場合
    get edit_account_activation_path(user.activation_token, email: 'wrong')   #おかしいurl(メール)取得
    assert_not is_logged_in?     #loginしていないか確認
    # 有効化トークンが正しい場合
    get edit_account_activation_path(user.activation_token, email: user.email) #正しいurl取得
    assert user.reload.activated?      #reloadしてactivatedがtrueか確認
    follow_redirect!               #showページに飛ばす
    assert_template 'users/show'		#showビューが表示されるか
    assert is_logged_in?				#ログイン状態になったか
  end
end
```

```assert_equal 1, ActionMailer::Base.deliveries.size```
は、配信されたメッセージがきっかり1つであるかどうかを確認する。
配列deliveriesは変数なので、setupメソッドでこれを初期化しておかないと、並行して行われる他のテストでメールが配信されたときにエラーが発生する

これでtestはpassする

####リファクタリングする

activateメソッドを作成してユーザーの有効化属性を更新し、send_activation_emailメソッドを作成して有効化メールを送信します。この新しいメソッドをリスト 11.35に示します。また、リファクタリングされたアプリケーションコードをリスト 11.36とリスト 11.37に示します。

Userモデルにユーザー有効化メソッドを追加する


```app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # アカウントを有効にする
  def activate
    #update_attribute(:activated,    true)  #activatedカラムをtrueにupdateする
    #update_attribute(:activated_at, Time.zone.now)     #activated_atを現在時刻にupdateする
     update_columns(activated: true, activated_at: Time.zone.now) #演習でこのように書き換える
  end

  # 有効化用のメールを送信する
  def send_activation_email
    UserMailer.account_activation(self).deliver_now   #メールを送る
  end

  private
    .
    .
    .
end
```

ユーザーモデルオブジェクトからメールを送信する

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      @user.send_activation_email     #user.rbで定義したsend_activation_emailを呼び出す（メール送る）
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end
  .
  .
  .
end

```

ユーザーモデルオブジェクト経由でアカウントを有効化する

```app/controllers/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      #user.rbで定義したactiveメソッドを呼び出す→activatedを更新
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
    end
  end
end
```

これでtestはpassする

###11.4 本番環境でのメール送信
開発環境ではなく、本番環境でメールを送信できるようにする
そのために無料のサービスを利用し、送信の設定をする
次にアプリの設定とデプロイをする

本番環境からメール送信するために、「Mailgun」というHerokuアドオンを利用

アプリケーションでMailgunアドオンを使うには、production環境のSMTPに情報を記入する

```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'   #自分のherokuのURLを入力
  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に上げて、heroku本番環境にあげる

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

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

MailgunのHerokuアドオンを追加するために、次のコマンドを実行

```$ heroku addons:create mailgun:starter```
　　クレカを事前に登録しておかないとエラーが出る

受信メールの認証を行います。以下のコマンドを打つと、Mailgun ダッシュボードのURLが表示されるのでブラウザで開きます。
```$ heroku addons:open mailgun
```
ブラウザの画面左側の「Sending」→「Domains」のリストにある「sandbox」で始まるサンドボックスドメインを選択
画面右側の「Authorized Recipients」から受信メールアドレスを認証し、本番環境でのメール送信準備は完了

あとは本番環境で新規アカウントを作成してみると正常に作動する

###まとめ

非常に難しい内容でしたが、1つ1つ意味を噛み砕いて行けばなんとかできました...