1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-03

#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つのカラムを追加するための
次のマイグレーションをコマンドラインで実行

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

マイグレーションを実行

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 = '.vfs.cloud9.us-east-2.amazonaws.com' # クラウドIDE
config.action_mailer.default_url_options = { host: host, protocol: 'https' }
※'はサーバーの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に(引数のメソッド)を渡しているため 同じ結果になる

もう1つ例

	>> 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とした場合

つまり

			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が表示されるのでブラウザで開きます。

open mailgun

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

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

###まとめ

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

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?