#目次
#1. はじめに
- この記事は、Rails初学者の工業大学三年生がRailsチュートリアルの学習記録を
つけるための記事です。 - 筆者自体がRailsやWebについて知識が少ないので、内容の解釈などに
間違いがある可能性があります。(その時はコメントで指摘してくださると助かります!) - Railsチュートリアル内ではRailsの内容以外にも、gitでのバージョン管理やHerokuを使ったデプロイも
学習しますが、gitに関しては既に私が学習済みのため学習記録には記述しません。 - 演習の記録も省略します。
#2. 第11章の概要
この章では、新規登録したユーザーに対してアカウントを有効化するステップを設けます。
ユーザーのメールアドレスにアカウントの有効化を行うためのリンクを示し、それがクリックされたら
アカウントが有効化され、ユーザーがアプリケーションを使用できるようになります。
これにより、メールアドレスの持ち主が登録したかどうかを確認できます。
- アカウント有効化機能の実装準備
- 実装までの流れと処理の流れ
- コールバックで処理を実行する
- メイラーを使用する
- メイラーを追加する
- 新規登録時にアカウント有効化のメールを送る
- アカウントを有効化する
- authenticated?メソッドを改良する
- editアクションで有効化を行う
- 有効化していないユーザーがログインできないようにする
#3. 学習内容
###1. アカウント有効化機能の実装準備
####1-1. 処理の流れと実装までの流れ
まず、アカウント有効化機能の処理の流れを確認します。
①ユーザーの初期ステータスを、「有効化されていない」にする
②ユーザー登録が行われた時に、有効化トークンと有効化ダイジェストを生成する
③有効化ダイジェストはデータベースに保存、有効化トークンはメールアドレスと一緒に
有効化用メールのリンクに含める
④ユーザーがメールのリンクをクリックしたら、メールアドレスからユーザーを検索し、
データベース内の有効化ダイジェストとリンク内の有効化トークンを比較して認証する
⑤ユーザーが認証できたら、ユーザーのステータスを「有効化済み」にする
以上の流れで処理が行われていきます。
このトークンとダイジェストを使用した仕組みは、以前に実装したパスワードの認証の仕組みと似ています。
次に実装までの流れを確認します。
①Userモデルに有効化ダイジェスト、有効化の状態、有効化が行われた日時を格納する属性を追加する
②新規登録時に有効化トークンと有効化ダイジェストを生成する
③有効化トークンとメールアドレスが含まれた有効化用リンクが書かれたメールを送信する
④リンクがクリックされたらステータスを変更し、アプリケーションが使用できるようにする
以上の流れでアカウント有効化機能の実装を行います。
これらの処理はアカウントの有効化という作業を「リソース」としてモデル化して実装します。
モデル化することにより、コントローラとアクションが使用できるようになります。
実装する機能ではデータベースの有効化ステータス属性を変更する処理がメインなので、
本来であればcreateアクションでその処理を実行します。
ですが、今回の場合はメールのリンクをクリックしたときに処理を実行します。
リンクをクリックしたときに発行されるリクエストは、POSTリクエストではなくGETリクエストになってしまうので、
この機能はeditアクションで実行することになります。
まず、アカウント有効化のリソースを作るために以下のコマンドでAccountActivationsコントローラを生成します。
rails generate controller AccountActivations
そして、メール内に書くリンクからeditアクションを実行するので、
editアクションへの名前付きルートを以下のコードをroutes.rbに追加します。
resources :account_activations, only: [:edit]
これにより、有効化の作業をリソース化できました。
editアクションの定義は後で行います。
次に、Userモデルへの属性の追加を行います。
ここでは、
有効化ダイジェストを格納するactivation_digest
有効化ステータスを格納するactivated
有効化された日時を格納するactivated_at
上記の3つの属性を以下のコマンドで追加します。
rails generate migration add_activation_to_users \ activation_digest:string activated:boolean activated_at:datetime
これらの属性を追加したUserモデルは以下のようになります。
属性名 | 型名 |
---|---|
id | integer |
name | string |
string | |
created_at | datetime |
updated_at | datetime |
password_digest | string |
remember_digest | string |
admin | boolean |
activation_digest | string |
activated | boolean |
activated_at | datetime |
ここまでで、アカウント有効化機能の実装に必要な変更ができました。
####1-2. コールバックで処理を実行する
ユーザーが新規登録を完了するにはアカウントの有効化が必要になるので、
オブジェクトが作成される前に、有効化トークンと有効化ダイジェストを生成して、
それを作成されたオブジェクトに渡さなければいけません。
このような、○○をする前に特定の処理を行うという際に使用するのがコールバックです。
以前、ユーザーをデータベースに保存する前に、メールアドレスを小文字に変換するという処理を実装しました。
そこでもbefore_saveコールバックというメソッドが使用されています。
ここでは、オブジェクトが作成される前に処理を実行したいので、
その時に使用するコールバックはbefore_createコールバックとなります。
今回はcreate_activation_digestというメソッドで、
有効化トークンと有効化ダイジェストを生成することになるので、コードは以下のようになります。
# アクセスしようとした URL を覚えておく
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token
before_save :downcase_email
before_create :create_activation_digest
validates :name, presence: true, length: { maximum: 50 }
private
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
###2. メイラーを使用する
####2-1. メイラーを追加する
メイラーとは、アプリケーションからメールを送信するために使用する機能で、
動作はコントローラと似ています。
メイラーはコントローラ内で、送信先を設定したり、インスタンス変数を定義して、
メールの内容を定義するメイラービューで使用することができます。
メイラービューは1つのメソッドに対して2つ生成されます。
1つはテキストメール用のビューで、もう1つはHTMLメール用のビューです。
メイラーの生成には以下のコマンドを実行します。
rails generate mailer UserMailer account_activation password_reset
このコマンドにより、アカウントの有効化用のメールを送信するaccount_activationメソッドと、
12章で使用する、パスワードの再設定用のメールを送信するpassword_resetの2つのメソッドを持った、
UserMailerが生成されました。
以下にaccount_activationメソッドの内容と、それに対応するメイラービュー(テキストメール用)を示します。
class UserMailer < ApplicationMailer
def account_activation(user)
@user = user
mail to: user.email, subject: "Account activation"
end
end
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) %>
1つ目のコードが、メールを送信するaccount_activationメソッドです。
ユーザーを引数にとり、そのメールアドレスを送信先に設定、subject: "Account activation"
は
メールの件名を設定しています。
メイラービューの重要な部分は、
<%= edit_account_activation_url(@user.activation_token, email: @user.email) %>
この埋め込みRubyで書かれた部分です。
まず、edit_account_activation_url
はaccount_activationコントローラのeditアクションへの名前付きルートです。
そして、第一引数に有効化トークン、第二引数にメールアドレスを渡しています。
なぜ、このように書くかというと、このリンクに有効化トークンとメールアドレスを含めるためです。
例えばユーザーの編集ページのリンクのコードは
edit_user_url(user)
というように書きます。
このコードから生成されるリンクはhttps://www.example.com/users/<userのid>/edit
です。
usersコントローラを表す部分とeditアクションを表す部分の間に、引数に渡したユーザーのidが入り、
paramsハッシュでparams[:id]で参照できます。
これと同じ様に今回のコードでは引数に有効化トークンに加えてemail: @user.emailというハッシュがあるので、
https://www.example.com/account_activations/<有効化トークン>/edit?email=<メールアドレス>
このようにコントローラとアクションの間に有効化トークンが入り、URLの末尾にクエリパラメータとしてメールアドレスが入ります。
editアクションでは生成されたリンクの内の有効化トークンとメールアドレスを、
params[:id], params[:email]で参照できます。
####2-2. 新規登録時にアカウント有効化のメールを送る
これでメールを送信する準備が整いました。
ユーザー登録を行うcreateアクションに、メールを送信する処理を加える変更を行います。
下に変更前のcreateアクションと変更後のcreateアクションを載せます。
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now # @userにメールを送信
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end
変更内容は、ログイン処理を行わずにメールを送っている点とそれに伴うメッセージの変更。
そして、リダイレクト先の変更の3つです。
なぜログイン処理を行わないかというと、ユーザー登録を完了してもアカウントの有効化を行うまで
ログインはできないようにしたいからです。
そして、元のリダイレクト先として設定されていたプロフィールページはログインしないと見れないページなので、
ここにリダイレクトさせる意味がなくなりその代わりとしてルートURLに変更しました。
###3. アカウントを有効化する
####3-1. authenticated?メソッドを改良する
ログイン状態の記憶の際に記憶トークンと記憶ダイジェストを比較するauthenticated?メソッドを、
有効化トークンと有効化ダイジェストを比較する処理にも流用します。
現段階で定義されているauthenticated?メソッドは以下のコードです。
# トークンがダイジェストと一致したら true を返す
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
このコードでは、remember_digestとremember_tokenを比較しています。
今回のアカウントの有効化ではactivation_digestとactivation_tokenを比較したいのですが、
authenticated?メソッド内のrememberの部分を変数として扱えると有効化の処理でもこのメソッドが使用できるようになります。
ここで使用するのがsendメソッドです。
sendメソッドは引数に、シンボルや文字列でメソッド名を渡すとメソッドが実行されます。
例:sendメソッドで配列の長さを取得する
>> a = [1, 2, 3]
>> a.length
=> 3
>> a.send(:length)
=> 3
>> a.send("length")
=> 3
上記の例ではsendメソッドでメソッドを実行していますが、sendメソッドではuser.send("attribute_digest")
のように書くと属性の値を呼び出すこともできます。
そして、この引数に渡しているのは文字列なので、変数を含められます。
user.send("#{attribute}_digest")
のように変数を含めて実行できるように変更を加えたauthenticated?メソッドが以下です。
# トークンがダイジェストと一致したら true を返す
def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end
元々1つだった引数が2つになり、第一引数に属性名を渡すことで、authenticated?メソッドが
有効化の処理にも流用できるようになりました。
####3-2. editアクションで有効化を行う
ここからは、先ほど改良したauthenticated?メソッドを使用して有効化を行えるようにします。
editアクションで、paramsハッシュからメールアドレスを受け取りユーザーを検索して有効化します。
以下のコードが有効化を行うeditアクションです。
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
このアクションは、メール内のリンクがクリックされた時に実行されます。
まず、リンク内のメールアドレスからユーザーを検索しています。
その次の条件分岐では、リンク内の有効化トークンと、データベース内の有効化ダイジェストが一致しているかつ、
まだ有効化されていないユーザーに対して有効化処理を行います。
なぜ、まだ有効化されていないという条件を組み込んでいるかというと、
有効化処理の後にログイン処理を行っているからです。
もし、有効化されているユーザーでも有効化処理を行えるようにすると、
送信されたメールやメール内のリンクが盗み出された時に、第三者にログインされてしまいます。
それを防ぐためにここでは2つの条件式を利用しています。
条件式を満たしたユーザーは、update_attributeメソッドで、有効化ステータスがtrueになり、
この処理を行った日時が記録されます。
####3-3. 有効化していないユーザーがログインできないようにする
ここまでで、新規登録したユーザーに対して、メールを送りってリンクがクリックされたら
有効化処理を行うという一連の流れが実装できました。
最後に既存のログイン機能に変更を加えます。
新しく追加された機能をログイン処理に対しても適用するために、
アカウントが有効化されていない場合はログインを許可しないという処理を加えていきます。
以下に変更前のログイン処理と変更後のログイン処理を載せます。
def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
params[:session][:remember_me] == '1' ? remember(user) : forget(user)
redirect_back_or user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
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
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
変更部分としては、ユーザーのパスワードの認証が終わった後にif user.activated?
という条件式を加えている部分です。
ここで現在の有効化ステータスを判定して、有効化されていた場合は変更前のコードと同じくログイン処理を実行します。
一方、有効化が行われていなかった場合はルートURLにリダイレクトさせてフラッシュメッセージで、
「Account not activated. Check your email for the activation link.」
と表示します。
このように有効化されていないユーザーのログインを拒否する処理は、
user.activated?メソッドで有効化ステータスを判定することで実装できます。
#4. 終わりに
この章ではアカウントの有効化の機能を追加しました。
今までいろんなアプリケーションでアカウント有効化を実際に利用していましたが、
なぜこの過程が必要なのかあまり分からないままでいました。
ですが、この章でこの過程の必要性とその機能がどのように実装されているのかが理解できました。
もうすでに手を付けている12章では、ここで実装したメイラーをまた使用してパスワードの再設定機能を実装していってます。