LoginSignup
1
1

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

マイグレーションを実行
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 = '.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とした場合

つまり
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つ意味を噛み砕いて行けばなんとかできました...

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