#アカウントの有効化
■第11章
この章では、アカウントを有効化するステップを新規登録の途中に差し込むことで、本当にそのメールアドレスの持ち主なのかどうかを確認できるようにしてみる。
##11.1 AccountActivationsリソース
セッション機能 を使って、アカウントの有効化という作業を「リソース」としてモデル化する。
###11.1.1 AccountActivationsコントローラ
AccountActivationsコントローラを生成。
$ rails generate controller AccountActivations
ルーティングにresources
行を追加。
resources :account_activations, only: [:edit]
###11.1.2 AccountActivationのデータモデル
ハッシュ化だったり、3つの属性が必要。
$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime
これで追加完了。activated
属性のデフォルトの論理値をfalse
にして、マイグレーションしておく。
Userモデルにアカウント有効化のコードを追加。
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 downcase_email
self.email = email.downcase
end
# 有効化トークンとダイジェストを作成および代入する
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end
end
サンプルデータとfixtureも更新しておく。
Time.zone.now
はRailsの組み込みヘルパーであり、サーバーのタイムゾーンに応じたタイムスタンプを返す。
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のユーザーも更新。テストもGREENに。
##11.2 アカウント有効化のメール送信
アカウント有効化メールの送信に必要なコードを追加していく。
###11.2.1 送信メールのテンプレート
rails generate
でメイラーの作成。
$ rails generate mailer UserMailer account_activation password_reset
これで今回必要となるaccount_activation
メソッドとpassword_reset
メソッドが生成された。
生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにする。mail
にsubject
キーを引数として渡す。
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
###11.2.2 送信メールのプレビュー
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができるらしい。これを利用するために、development環境の設定に手を加える。
Rails.application.configure do
.
.
.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :test
host = 'example.com' # ここをコピペすると失敗します。自分の環境に合わせてください。
config.action_mailer.default_url_options = { host: host, protocol: 'https' }
.
.
.
end
hostには開発環境のurlで、https://を抜いたバージョンをいれとく。
次はUserメイラーのプレビューファイルを更新する。
# 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
###11.2.3 送信メールのテスト
メールプレビューのテストも作成。
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
assert_match user.activation_token, mail.body.encoded
assert_match CGI.escape(user.email), mail.body.encoded
end
end
テストファイル内のドメイン設定に以下を追加。
config.action_mailer.default_url_options = { host: 'example.com' }
ここも開発環境のURL。テストはGREENに。
###11.2.4 ユーザーのcreateアクションを更新
元々プロフィールページにリダイレクトしていたが、ルートURLに移動するように変更する。
ユーザー登録にアカウント有効化を追加。
class UsersController < ApplicationController
.
.
.
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
.
.
.
end
このままではエラーになるので、統合のテストの一部をコメントアウトしておく。
##11.3 アカウントを有効化する
今度はAccountActivationsコントローラのedit
アクションを書いていく。
###11.3.1 authenticated?
メソッドの抽象化
プログラムでプログラムを作成することをメタプログラミングという。
authenticated?
メソッドを書き換える。
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
このままだとテストは失敗する。authenticated?
が古いままになっており、引数も2つではなくまだ1つのままだから。メソッドを書き換えたら無事てテストはGREENに。
###11.3.2 editアクションで有効化
editアクションでアカウントが作成されるようにして、一度リンクをクリックして有効化されたら有効化されないようにする。
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
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
###11.3.3 有効化のテストとリファクタリング
アカウント有効化の統合テストを追加する。正しい情報でユーザー登録を行った場合のテストは既にあるため、若干手を加える。
統合テストの中身を少し弄って、テストはGREENに。
activate
メソッドを作成してユーザーの有効化属性を更新し、send_activation_email
メソッドを作成するため、リファクタリングして終了。
##11.4 本番環境でのメール送信
ここまでの実装で、development環境におけるアカウント有効化の流れは完成した。次はサンプルアプリケーションの設定を変更し、production(実行)環境で実際にメールを送信してみる。
なんかうまくできない。。
Heroku周りは本当にわからないまま時間が吸われていくので、一旦スキップします。
##感想
Railsはとにかく便利なモノ。書き方も正しいものがあるっぽい。とにかく難しかったです。。