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


リファクタリング

# アカウントを有効にする
def activate
update_attribute(:activated, true)
update_attribute(:activated_at, Time.zone.now)
end

# 有効化用のメールを送信する
def send_activation_email
UserMailer.account_activation(self).deliver_now
end


  private
    .
    .
    .
end
 
app/controllers/users_controller.rb
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
今回の場合は環境変数が自動で設定されるみたいなのでこれら必要ないみたい。

$ heroku addons:open mailgun

Mailgun ダッシュボードのURLが表示されるのでブラウザで開きます。

こちらで受信するメールアドレスを認証します。画面左側の「Sending」→「Domains」のリストにある「sandbox」で始まるサンドボックスドメインを選択します。画面右側の「Authorized Recipients」から受信メールアドレスを認証し、本番環境でのメール送信準備は完了です。

これは実際に存在するメールにしなければメールが来ない。今回であればgmailのメールを受信するようにした。
その結果登録したgmail 宛にメールが来た。