Railsチュートリアル11章まとめ。サインアップでのメールによる認証編

Railsチュートリアル 11章を終えました。
11章ではサインアップ時やその他メールの定型文を送る方法を見ていきます

railstutorial.jp

メールアドレス有効化の概要

ユーザ新規作成時の有効化の手順はRailsチュートリアル10章までは、サインアップ画面にユーザ情報を入力することで有効化できましたが、入力されたメールアドレスが有効かどうかまでは調べれませんでした。
ここではメールアドレスが有効かどうかを調べるために、実際にメールを送り、そのメールに記載しているユーザ毎発行の有効化リンクをクリックすることで、有効化させます。


具体的には

1.Userモデルに有効化カラム(boolean)を作成し、このカラムが有効化(true)じゃないとユーザを無効化する

2.ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。

3.有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく

4.ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する

5.ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。



この過程はユーザ登録時のパスワード認証とよく似た仕組みで実装できます

AccountActivationsリソース

有効化のメールにはeditアクションへのURLを発行することになりますので、そのルーティングが必要になります。
route.rb

  resources :account_activations, only: [:edit]

データモデル

次に、モデルに[有効化カラム]と共に、ユーザ登録時同様に、仮想属性として[有効化トークン]と[有効化ダイジェスト]を用意して認証を行うことで有効化させます

$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime

これを実行することでUserモデルは下記のようになります

[チュートリアル11章Userモデル]
チュートリアル11章Userモデル

有効化コールバック

[有効化トークン]とそこから生成する[有効化ダイジェスト]の作成はUserモデルで行います。

app/model/user.rb

  # 有効化トークンとダイジェストを作成および代入する
  def create_activation_digest
    self.activation_token  = User.new_token
    self.activation_digest = User.digest(activation_token)
  end

これの実行はユーザーを作成する時点で実行すれば良いです。
具体的にはモデルに対して、ユーザを作成するときに動作するbefore_createコールバックを設定することで実行できます
以前に、mailアドレスを小文字化するdowncase.emailbefore_saveコールバックとして定義しましたが
これはユーザを登録するときに動作するものになります


before_actionはコントローラにて、とあるアクションの前に実施されるメソッドの設定になります


メイラー作成

続いてメイラーを作成していきます。具体的には$ rails gで作成します。

$ rails generate mailer UserMailer account_activation password_reset

テンプレート作成

メイラーによって、text用のテンプレートとHTML用のテンプレートの2つ自動作成されます
後述する@userをuser_mailerに定義して、これをテンプレートに使用します。

textテンプレート

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

htmlテンプレート

<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_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com というトークンとURLの末尾で疑問符「?」に続けてキーと値のペアを記述したクエリパラメータ付きのURLが作成されます
このURLをメールからクリックすることで、activated属性を更新できます

メイラー作成(mailer.rb)

また、$rails gを行うことでApplicationメイラーと、それを継承するUserメイラーが生成されます
どちらもこの課題用に少しいじります

app/mailers/application_mailer.rb

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

app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.account_activation.subject
  #
  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end

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

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



コントローラ編集

最後にサインアップ時にメールを送るアクションをコントローラに追加します

app/controller/users_controller.rb

  def create
    @user = User.new(user_params)
    if @user.save
      @user.send_activation_email
      flash[:info] = "Please check your email to activate your account."
      redirect_to root_url
    else
      render 'new'
    end
  end

app/model/user.rb

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

プレビュー

ここまでくればサインアップでメールは送れますが、メールをプレビューしたいだけだと、わざわざサインアップするのは面倒です
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができます。
そのためにはローカル環境設定を少しいじる必要があります

config/environments/development.rb

  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'localhost:3000'                     # ローカル環境
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }

もしクラウド環境で開発している場合host = 'rails-tutorial-mhartl.c9users.io' # クラウドIDEとすれば良いです


次に自動生成されたuser_mailer_previewを少しいじります。
この課題の場合、メールにユーザ情報やトークンを入れているのでプレビューにも入れます 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
    UserMailer.password_reset
  end
end

user_mailer_preview.rbのコメント中にあるhttp://localhost:3000/rails/mailers/user_mailer/account_activationにアクセスすることで、メールのプレビューを見ることができます

ユーザ有効化

メール中の有効かリンクをクリックすることで、ユーザの有効化する処理を書いていきます
メールのリンクをクリックした際に送られてくる有効化トークンはparams[:id]で取り出せるので、それをダイジェストと一致するかを見ます

app/controllers/account_activations_controller.rb

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

app/model/user.rb

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


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

sendメソッド

userモデル中のdigest = send("#{attribute}_digest")について、
sendメソッドを"#{attribute}_digest"とすることでattributeに値を入れると別々のメソッドにアクセスできます
例えばattributeの値が"remember"であれば記憶ダイジェストを、
"activation"であれば有効化ダイジェストを呼び出します

最後に

メールによる認証はここまでやっていると、だんだんと理解することができそうです
あと3章で終わりですね

参考

いつもここの方にはまとめ方がうまいので感謝です

www.masalog.site