##はじめに
この記事ではrailsチュートリアルを進めていって、引っかかったところや、つまづいたところを引っ張り出して要約しています。
##アカウントの有効化
ユーザー登録はすでに実装すみだが、よくある送られてきたメールアドレスのリンクをクリックすると登録完了というような処理を実装する。
1:ユーザーの初期状態は「有効化されていない」(unactivated) にしておく。
2:ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
3:有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。
4:ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5:ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。
というものとなる。
##AccountActivationsリソース
セッション機能を使って、アカウントの有効化という作業を「リソース」としてモデル化する。
###AccountActivationsコントローラ
AccountActivationsリソースを作るために、まずは AccountActivationsコントローラ
を生成する。
$ rails generate controller AccountActivations
ここで今回定義するeditを記述できそうだが、記述してしまうとテストやビューができてしまうから記述はしない。
次に名前付きルートを扱えるようにするためにルーティングにアカウント有効化用のresources行を追加する。
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
##AccountActivationのデータモデル
冒頭で少し説明したように、有効化のメールには一意の有効化トークンが必要となる。
今回もパスワード実装などの時と同じように仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するようにする。
まずは usersテーブル
に3つの属性を新しく追加する。
$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime
生成されたマイグレーションファイルの追加修正としては、activated属性に default:false
を設定しておく。
(adminのように)
class AddActivationToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :activation_digest, :string
add_column :users, :activated, :boolean, default: false
add_column :users, :activated_at, :datetime
end
end
マイグレーションを実行。
$ rails db:migrate
###Activationトークンのコールバック
有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく必要がある。
メールアドレスをデータベースに保存する際はbefore_saveコールバックを利用し、オブジェクトが保存される直前、オブジェクトの作成時や更新時にそのコールバックが呼び出された。
今回は、オブジェクトが作成されたときだけコールバックを呼び出したいから、before_createコールバック
を使用する。
よって、Userモデル
にアカウント有効化のコードを追加すると以下のようになる。
class User < ApplicationRecord
#attr_accessorにactivation_token追加
attr_accessor :remember_token, :activation_token
#それぞれbeforeコールバック
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
有効化トークンは本質的に仮のものでなければならないから、このモデルのattr_accessor
にもう1つ追加する。
また、create_activation_digest
メソッドはUserモデル内でしか使用しないので、private
キーワードの中に定義しておく。
##アカウント有効化のメール送信
データのモデル化が終わった。
次にアカウント有効化メールの送信に必要なコードを書いていく。
このメソッドではAction Mailerライブラリを使ってUserのメイラーを追加する。
このメイラーはUsersコントローラのcreateアクションで有効化リンクをメール送信するために扱う事になる。
###送信メールのテンプレート
メイラーは、モデルやコントローラと同様に rails generate
で生成できる。
$ rails generate mailer UserMailer account_activation password_reset
この一行で今回必要となる account_activationメソッド
と、第12章(パスワード再設定)で必要となる password_resetメソッド
を生成することができた。
また、上記の他にもメイラーごとに2つのビューファイルが作られる。(テキストメール用とHTMLメール用)
account_activationメソッドのビューファイルを以下に示す。
テキストメール:
UserMailer#account_activation
<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
HTMLメール:
<h1>UserMailer#account_activation</h1>
<p>
<%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
また生成された2つのメイラーはこちら。
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout 'mailer'
end
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
@greeting = "Hi"
mail to: "to@example.org"
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
この2つにはデフォルトのfromアドレスや、宛先メールアドレスのような情報がある。
この2つのメイラーのテンプレートをカスタマイズして実際に使用できるものにしていく。
class ApplicationMailer < ActionMailer::Base
#修正
default from: "noreply@example.com"
layout 'mailer'
end
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
この修正により、user.emailにメール送信し、件名も追加することができた。
さらにメールにはカスタムの有効化リンクを追加する。
この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにするため、リンクにはメールアドレスとトークンを両方含めておく必要がある。
よって、2つのファイルは以下のようになる。
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) %>
<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) %>
##ユーザーのcreateアクションを更新
あとはユーザー登録を行うcreateアクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことができる。
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
変更前は、ユーザーのプロフィールページにリダイレクトしていたが、アカウント有効化を実装するうえでは無意味な動作なので、 リダイレクト先をルートURLに変更する。
##アカウント有効化
このようにして、メールができたので、今度はAccountActivationsコントローラの editアクション
を定義していく。
###authenticated?メソッドの抽象化
以前は有効化トークンとメールをそれぞれparams[:id]とparams[:email]で参照できた。
パスワードのモデルと記憶トークンで学んだことを元に、次のようなコードでユーザーを検索して認証することにする。
user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])
ただし、このメソッドは記憶トークン用なので今は正常に動作しない。
よって、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
これにより、current_userメソッド
の引数も修正する必要がある。
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])
log_in user
@current_user = user
end
end
end
.
.
.
end
###editアクション
authenticated?
が更新されたので、editアクション
を定義できるようになった。
既に有効になっているユーザーを誤って再度有効化しないために !user.activated?
というコードを付け足す。
また、トークンが無効になるようなことは実際にはめったにないが、もしそうなった場合はルートURLにリダイレクトされる仕組みを書いている。
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
これにより、送られてきたurlをブラウザ上で開くと、
"Account activated!"
というメッセージを表示して、ログイン画面に映る。
ただ、まだユーザーログインの方法を変更していないから有効化はまだ機能してない。
よって、createアクションで有効化されたユーザーだけをログインさせるように追加修正する。
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
これで、ユーザー有効化機能の大まかなところは実装できた。
あとはリファクタリングでよりソースを綺麗にすることができるが、ここでは省略。
##本番環境でのメール送信
ここまでの実装で、development環境
におけるアカウント有効化の流れは完成した。
次は、サンプルアプリケーションの設定を変更し、production環境
で実際にメールを送信できるようにする。
本番環境からメール送信するために、「SendGrid」
というHerokuアドオンを利用してアカウントを検証する。
その中でも今回は「starter tier」
というサービスを使うので以下のコマンドでアドオンをアプリケーションに追加する。
$ heroku addons:create sendgrid:starter
アプリケーションで SendGridアドオン
を使うには、production環境のSMTPに情報を記入する必要がある。
Rails.application.configure do
.
.
.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.delivery_method = :smtp
#それぞれのホストを入力
host = '<your heroku app>.herokuapp.com'
config.action_mailer.default_url_options = { host: host }
ActionMailer::Base.smtp_settings = {
:address => 'smtp.sendgrid.net',
:port => '587',
:authentication => :plain,
:user_name => ENV['SENDGRID_USERNAME'],
:password => ENV['SENDGRID_PASSWORD'],
:domain => 'heroku.com',
:enable_starttls_auto => true
}
.
.
.
end
また、SendGridアカウントのuser_nameとpassword設定をコマンドで確認できるが上記のファイルはそうした変数はSendGridアドオンが自動的に設定されているから現時点で修正する必要はない。
確認するコマンドを以下に示しておく。
$ heroku config:get SENDGRID_USERNAME
$ heroku config:get SENDGRID_PASSWORD
##さいごに
第11章では、実際にチュートリアルを進める中で一番時間をかけてしまった、、、
メールが届かないなどのトラブルがあったが、時間をかけて焦らず理解していこうと思う。
次↓
https://qiita.com/jonnyjonnyj1397/items/a2c0e3f87113854e2e30