Railsチュートリアル11章 アカウントの有効化
アカウントの有効化
ユーザーを新規登録する際にメールを送って、メール内に貼ってあるリンクをクリックすることで、初めてログインできるようになる。
全体の流れ
ユーザーがsave出来たら、before_createでactivation_tokenを作り、それをdigest化してハッシュにしたactivation_digestを作る(self.activation_digestとなっているのでuserのカラム内に入る)。そしてuserコントローラー内でメールを送る。この段階でuserはcreateされている。このメール内のedit_pathに行くとauthenticatedで、先ほどdigest化したやつとactivation_tokenが合っているか確かめる。合っていたらactivationコントローラー内のeditアクションでactivatedをtrueにする。セッションコントローラーでactivatedがtrueであればログインできる。falseであればログインできないように記述する。これによって初めてログインできるようになる。
--------------------------------------------------------------------------------------------------------------------
まずはacctount_activationコントローラー作る
$ rails generate controller AccountActivations
ルーティング
edit設定
config/routes.rb
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
マイグレーション
activation_digestカラム等作る
$ rails generate migration add_activation_to_users \ > activation_digest:string activated:boolean activated_at:datetime
class AddActivationToUsers < ActiveRecord::Migration[6.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_token,activation_digestを作る記述をかく。
app/models/user.rb
*before_saveのところは追加しているのではなく変更しているだけ。
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
メールを送信するメイラーを作る
$ rails generate mailer UserMailer account_activation password_reset
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
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) %>
emailの情報からuserのactivation_tokenを持ってきている。
activation_digestはカラムに入れたが、activation_tokenはカラムに入れてないから@userで持ってこれないからだと思う。
カラムに入れない理由はdbで情報を盗まれたら困るため。そのためハッシュ化したactivation_digestをカラムに入れてる。
送信メールのプレビュー
メールを送らずともメールの画面を確認できる。
config/environments/development.rb
Rails.application.configure do
.
.
.
config.action_mailer.raise_delivery_errors = false
host = 'example.com' # ここをコピペすると失敗します。自分の環境のホストに変えてください。
# クラウドIDEの場合は以下をお使いください
config.action_mailer.default_url_options = { host: host, protocol: 'https' }
# localhostで開発している場合は以下をお使いください
# config.action_mailer.default_url_options = { host: host, protocol: 'http' }
.
.
.
end
localhostの場合は
host = 'localhost:3000' # ローカル環境
config.action_mailer.default_url_options = { host: host, protocol: 'http' }
test/mailers/previews/user_mailer_preview.rb
# 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
test/mailers/previews/user_mailer_preview.rb
ユーザーのcreateアクションにメール送信記述を書く。
app/controllers/users_controller.rb
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が書いてあるのでそれを持ってきて貼るとメールが確認できる。
authenticated?メソッド
app/models/user.rb
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
この形にしているのは、acctivation_digestの他に、クッキーのremember_digestでも使うため。
どちらでも対応できるようにするためsendメソッドを用いて、引数で送られてきたやつのdigestを取り扱っている。
app/helpers/sessions_helper.rb
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
引数を2つに変更する。
app/controllers/account_activations_controller.rb
メール内のedit_pathをクリックしたときのアクション
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
app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
activatedがtrueであれば、ログインできるようにsessionコントローラーに記述する
def new
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
def destroy
log_out if logged_in?
redirect_to root_url
end
end
リファクタリング
private
.
.
.
end
class UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save @user.send_activation_email flash[:info] = "Please check your email to activate your account." redirect_to root_url else render 'new' end end . . . end
app/controllers/account_activations_controller.rb
class AccountActivationsController < ApplicationController def edit user = User.find_by(email: params[:email]) if user && !user.activated? && user.authenticated?(:activation, params[:id]) user.activate log_in user flash[:success] = "Account activated!" redirect_to user else flash[:danger] = "Invalid activation link" redirect_to root_url end end end
有効なユーザーだけを表示するコードのテンプレート
app/controllers/users_controller.rb
class UsersController < ApplicationController . . . def index @users = User.where(activated: true).paginate(page: params[:page]) end def show @user = User.find(params[:id]) redirect_to root_url and return unless activated? end . . . end
本番環境でのメール送信
mailgunを使用する
config/environments/production.rb
Rails.application.configure do . . . config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '<あなたのHerokuサブドメイン名>.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :port => ENV['MAILGUN_SMTP_PORT'], :address => ENV['MAILGUN_SMTP_SERVER'], :user_name => ENV['MAILGUN_SMTP_LOGIN'], :password => ENV['MAILGUN_SMTP_PASSWORD'], :domain => host, :authentication => :plain, } . . . end
herokuのurlでok
$ rails test $ git add -A $ git commit -m "Add account activation" $ git checkout master $ git merge account-activation
$ rails test $ git push $ git push heroku $ heroku run rails db:migrate
$ heroku addons:create mailgun:starter
MailgunのHerokuアドオンを追加する
$ heroku config:get MAILGUN_SMTP_LOGIN $ heroku config:get MAILGUN_SMTP_PASSWORD
今回の場合は環境変数が自動で設定されるみたいなのでこれら必要ないみたい。