- あらかじめDBには有効化されていない状態のレコードがある
- 顧客ID、メールアドレス、電話番号を入力すると有効化メールが送信される
- リンクを踏んで有効化、ユーザー情報更新、ログイン
という流れを実装したのでまとめます。
前提
- customersテーブルには
activation_digest
、activated_at
の2つのカラムが必要(無い場合は追加)
Model
-
attr_accessor
でactivation_token
属性を有効化 - トークンとダイジェストを生成、代入するメソッドを作成
- 引数にトークンを取って、ダイジェストと比較するメソッドを作成
app/models/customer.rb
class Customer < ApplicationRecord
attr_accessor :activation_token
.
.
# 引数のハッシュ値を返す
def self.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# ランダムトークン生成
def self.new_token
SecureRandom.urlsafe_base64
end
# 有効化トークンとダイジェストを作成及び代入
def create_activation_digest
self.activation_token = self.class.new_token
self.activation_digest = self.class.digest(activation_token)
end
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(activation_token)
return false if activation_digest.nil?
BCrypt::Password.new(activation_digest).is_password?(activation_token)
end
end
Mailer
インスタンス変数を引数にとり、メールアドレスやトークンを抽出して送信します。
なお、本番環境のメール送信部分は未完成です。サーバーのログからメールの文面を確認してます。
【Rails】メールを送信する(ローカル)
app/mailers/customer_mailer.rb
class CustomerMailer < ApplicationMailer
def account_activation(customer)
@customer = customer
mail to: customer.email, subject: 'アカウント有効化'
end
end
app/views/customer_mailer/account_activation.text.erb
以下のアドレスにアクセスし、アカウントを有効化してください。
<%= edit_account_activation_url(id: @customer.activation_token, email: @customer.email) %>
Controller
- newアクション:メールアドレス等からユーザー(アクティベートされてない)の検索フォームを表示する
- createアクション:該当するユーザーが見つかったら、トークンとダイジェストを生成し、メールを送信
- sentアクション:「メールを送信しました」という画面を表示する
- editアクション:トークンとダイジェストを比較し、一致したらユーザー登録フォームを表示
- updateアクション:ユーザー情報更新 + アカウント有効化
app/controllers/account_activations_controller.rb
class AccountActivationsController < PublicController
#メールアドレス等からユーザー(アクティベートされてない)の検索フォームを表示する
def new
@customer = Customer.new
end
#該当するユーザーが見つかったら、トークンとダイジェストを生成し、メールを送信
def create
customer_id = params[:customer][:customer_id]
email = params[:customer][:email]
phone = params[:customer][:phone]
@customer = Customer.find_by('(customer_id = ?) AND (email = ?) AND (phone = ?)', customer_id, email, phone)
unless @customer
flash.now[:danger] = '該当するユーザーが見つかりませんでした'
@customer = Customer.new
render :new and return
end
@customer.create_activation_digest
@customer.save
CustomerMailer.account_activation(@customer).deliver_now
redirect_to account_activations_sent_url(customer_id: @customer.id)
end
#「メールを送信しました」という画面を表示する
def sent
@customer = Customer.find_by(customer_id: params[:customer_id])
end
#トークンとダイジェストを比較し、一致したらユーザー登録フォームを表示
def edit
@customer = Customer.find_by(email: params[:email])
unless @customer
flash[:danger] = 'ユーザーが見つかりませんでした'
redirect_to login_url and return
end
unless @customer.activated_at.nil?
flash[:danger] = 'このアカウントはすでに有効化されています'
redirect_to login_url and return
end
unless @customer&.authenticated?(params[:id])
flash[:danger] = 'エラーが発生しました。もう一度やり直してください。'
redirect_to login_url and return
end
end
#ユーザー情報更新 + アカウント有効化
def update
@customer = Customer.find_by(customer_id: params[:id])
unless activation_params[:password].length >= 8
flash.now[:danger] = 'パスワードは8文字以上で入力してください'
render :edit and return
end
if @customer.update(activation_params)
@customer.update_attributes(activated_at: Time.zone.now)
log_in @customer
render 'account_activations/complete'
else
flash.now[:danger] = '入力内容に誤りがあります'
render :edit
end
end
private
def activation_params
params.require(:customer).permit(:customer_id, :email, :nickname, :phone, :password, :password_confirmation)
end
end
View
app/views/account_activations/new.html.erb
<%= form_with model: @customer, url: '/account_activations', local: true do |f| %>
<%= f.text_field :customer_id %>
<%= f.text_field :phone %>
<%= f.text_field :email %>
<%= f.submit value: '送信' %>
<% end %>
app/views/account_activations/sent.html.erb
<p>
ご入力いただいたメールアドレス(<%= @customer.email %>)に、確認用メールを送信しました。
メール本文に記載されているURLを開き、会員登録を完了させてください。
</p>
app/views/account_activations/edit.html.erb
<%= form_with model: @customer, url: "/account_activations/#{@customer.customer_id}", local: true do |f| %>
<%= f.text_field :customer_id, readonly: true %>
<%= f.text_field :phone, readonly: true %>
<%= f.text_field :email, readonly: true %>
<%= f.password_field :password, minlength: '8', required: 'true' %>
<%= f.password_field :password_confirmation, minlength: '8', required: 'true' %>
<%= f.text_field :nickname %>
<%= f.submit '登録' %>
<% end %>
app/views/account_activations/complete.html.erb
<p class="box__toptxt">ご登録が完了いたしました。</p>
<%= link_to 'トップページ', root_path %>
ルーティング
config/routes.rb
resources :account_activations, only: %i[new create edit update]
get '/account_activations/sent', to: 'account_activations#sent'
問題点
Modelでパスワードに入れる値に制限をかけたら、editアクションで@customer
を取得することができなくなってしまったので、仕方なくコントローラーでバリデーションの処理を実装しました。