まさ@ブログ書き込み中

まさ@ブログ書き込み中

まさの旅、英語、プログラミング、プライベートについて、色々記録しています。

皆にドヤ顔できるアカウントの有効化【Railsチュートリアル11章】

 

こんにちは。運動を習慣化させたいまさです。

 

今回はRailsチュートリアルの11章「アカウントの有効化」についてまとめていきたいと思います。

 

アカウントの有効化とは、サインアップした際に利用したメールアドレスにリンクを送り、そのリンクをクリックしたらアカウントが利用できるようにする仕組みのことです。この仕組みを利用すれば、メールアドレスが本物かどうかをチェックすることができます。

 

今回も大切なポイントだけまとめていきます。

ではさっそくいきましょう!

 

 

段取り

本章によると、アカウントを有効かする段取りは、以下のようになっています。

 

  1. ユーザーの初期状態は「有効化されていない」(unactivated) にしておく。
  2. ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
  3. 有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。
  4. ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
  5. ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。

 

パスワードの認証やユーザーの記憶(Remember me機能)と似ているところがありますね。

 

 

AccountActivationsリソース

コントローラ

いつもどおり、機能ごとにコントローラーを作るのでAccountActivationsコントローラを作りました。

$ rails generate controller AccountActivations

 

ルーティングは、以下のようにeditアクションだけに結びつくようにしておきます。

config/routes.rb

  resources :account_activations, only: [:edit]

 

データモデル

さて、アカウントを有効化させるにあたってどのようなデータモデルを作るべきなのでしょうか。

 

先ほども述べたとおりパスワードやユーザー記憶のように有効化トークンと有効化ダイジェストを用意して認証を行うことでアカウントを有効化させます。

 

そうなると、パスワードや記憶トークンを扱うように「仮想属性」としてuser.activation_tokenがあり、データベースに保存されるべきはuser.activation_digestだということは想像できそうですね。

 

また、user.activated?で有効化されているかどうかを見分けるために論理値を取るactivated属性を設定します。これは論理値を取る(boolean型の)admin属性を用意してadmin?メソッドを使ったことに似ていますな。Ruby(またはRails)では論理値を取る属性名+?でその属性の値をチェックできるのかな?うーんわからん。

 

とにかく、以上を踏まえたデータモデルでマイグレーションファイルを作成します。

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

 

コールバック

ユーザー登録の際にメールアドレスを全て小文字にしたbefore_saveコールバックのように、今回もbefore_createコールバックを設定します。ちなみに、コールバックとは「ある特定の時点で呼び出されるメソッド」という点で、before_actionメソッドとは違うみたいですね。

 

before_saveコールバックは保存の時点で実行され、before_createコールバックはユーザーを作成する時点で実行されます。対してbefore_actionは対象のアクションが実行される前に実行されるのでしたね。

 

before_createコールバックはユーザーを作成する時点で有効化トークンとダイジェストをつくるために設定されます。

app/models/user.rb

before_create :create_activation_digest
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end

 

 

アカウント有効化のメール送信

必要なカラム(属性)やデータ型を決めたり、コールバックメソッドも定義してデータのモデル化が終わったので、次はアカウント有効化のメール送信について見ていきましょう。

 

メイラーの生成

Action Mailerライブラリというものを使うことでメール送信ができるみたいなので、まずはメイラーを生成します。

$ rails generate mailer UserMailer account_activation password_reset

 

password_resetメソッドは12章で必要になるみたいなので、いまは気にしないでください。

 

メイラーは、生成したメイラー毎にビューのテンプレートが2つ(テキストメール用・HTML用)ずつ生成されます。

app/views/user_mailer/account_activation.text.erb

UserMailer#account_activation

<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb

app/views/user_mailer/account_activation.html.erb

<h1>UserMailer#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>

 

また、メイラーにはアプリケーションメイラーとユーザーメイラーが生成されるので、ちょこちょこ今回の目的のために修正します。

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
  def account_activation(user)
    @user = user
    mail to: user.email, subject: "Account activation"
  end

  def password_reset
    @greeting = "Hi"

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

 

継承関係からわかるように、UserMailerはApplicationMailerを継承しているので、メイラー全体として重要なこと(送信元やレイアウトの指定)はApplicationMailerに書いて、アカウントの有効化に関するメイラーの設定はUserMailerに書きそうだとわかります。

 

有効化のしくみ

さて、ここからがとっても面白いところです。

 

ここから僕らが使っているいろんなサービスのメールアドレス認証と同じように、メールの文章(ビュー)に有効化リンクをつくりたいのですが、そもそもどうやってリンクを踏むだけで有効化が行われるのでしょうか?

 

パスワードの場合はパスワードをブラウザ側からPOSTし、受け取ったRailsがダイジェストに変換してデータベースからマッチするものを見つけ出して認証しました。

ユーザーを記憶させる永続的セッションではcookiesが記憶トークン(cookies[:remember_token])を勝手にRailsサーバーに渡して、同じように探し出したのでした。

 

前例の二つはユーザーであれブラウザであれ、データの送信が行われたのですが、今回はメールに掲載されたリンクをクリックするだけなので、HTTPリクエストとしてはGETメソッドが使われ、RailsサーバーにはURIしか届きません。

 

そのため、activated属性を更新するのだからupdateアクションにルーティングすると言いたいところですが、editアクションで、GETリクエストで送られてきたURIをうまく利用する形でアカウントの有効化を行うのです。

 

長々と説明してきましたが、これらを踏まえて、以下のようにビューを変更します。

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


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)

は、名前付きルーティングにユーザーの有効化トークンとメールを与えています。

 

上のコードは以下のように変換されます。

account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

Railsでクエリパラメーター(URIの末尾に、?に続けてキーと値のペアを書いたもの)を設定するには名前付きルートに対してハッシュを与えればいいみたいです。

 

ユーザーのcreateアクションを更新する

さて、メイラーをアプリケーションで使うために以下のようにユーザーコントローラーを変更します。

app/controllers/users_controller.rb

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

 

account_activationメソッドは引数として与えられたユーザーにメールを送るメソッドでしたね。

 

 

アカウントを有効化する

データのモデル化が終わり、メールも送信できるようになりました。ついに、AccountActivationsコントローラーのeditアクションを書いて有効化を行います。

 

ユーザーの有効化にあたって一つ確認したいことがあります。有効化メールのリンクをクリックしたユーザーは以下のようなリクエストをRailsサーバーに送ることを思い出してください。

edit_account_activation_url(@user.activation_token, email: @user.email)

そしてこれは、以下のように変換されるのでした。

http://www.example.com/account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

 

それは、いわゆる

edit_user_url(user)

http://www.example.com/users/1/edit

と変換されるのと同じです。

 

何が言いたいかというと、ユーザーがメールのリンクをクリックした際に送られてくるデータ(params)でいうと有効化トークンは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.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

 

user.authenticated?メソッドには引数としてparams[:id]が渡されていますが、それは有効化トークンを渡しています。

 

記憶トークンを受け取ってダイジェストに変換し、認証を行っていたauthenticated?メソッドは以下のように抽象化し、有効化トークンを渡されてもうまく機能するように利用できるようにしました。

app/models/user.rb

def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

 

sendメソッドとは「渡されたオブジェクトに「メッセージを送る」ことによって、呼び出すメソッドを動的に決めることができ」るメソッドです。

 

このメソッドを使うことで、引数attributeの値によって、メソッドを変えることができます。attributeの値が"remember"であれば記憶ダイジェストを、"activation"であれば有効化ダイジェストを呼び出すわけですね。ちなみに、sendメソッドのレシーバは省略されている「self」、つまりauthenticated?メソッドを呼び出したインスタンス自身です。

 

 

 

 

以上で11章の大まかな流れを踏まえたまとめは終わりです。

長かったRailsチュートリアルも残る3章になりました。最後まで集中して取り組んでいきたいと思います!

 

ではまた。