アカウントの有効化 | rails チュートリアル 17

Ruby on Rails チュートリアル:実例を使って Rails を学ぼう第11章 アカウントの有効化の勉強メモです。

AccountActivationsリソース

本当にそのメールアドレスの持ち主なのかどうかを確認するための仕組み作成のために、AccountActivationsコントローラーを作成する。

$ bundle exec rails generate controller AccountActivations
Running via Spring preloader in process 81043
      create  app/controllers/account_activations_controller.rb
      invoke  erb
      create    app/views/account_activations
      invoke  test_unit
      create    test/controllers/account_activations_controller_test.rb
      invoke  helper
      create    app/helpers/account_activations_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/account_activations.coffee
      invoke    scss
      create      app/assets/stylesheets/account_activations.scss

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'
  post   '/signup',  to: 'users#create'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'

  resources :users
  patch  '/users/:id/edit', to: 'users#update'

  resources :account_activations, only: [:edit]
end

アカウント有効化のメールには、「edit_account_activation_url(activation_token, …)」を含めないといけないらしい。

only: [:edit] を付けるか、つけないかの違いは以下。

$ show-routes
                 Prefix Verb   URI Pattern                             Controller#Action
                   root GET    /                                       static_pages#home
                   help GET    /help(.:format)                         static_pages#help
                  about GET    /about(.:format)                        static_pages#about
                contact GET    /contact(.:format)                      static_pages#contact
                 signup GET    /signup(.:format)                       users#new
                        POST   /signup(.:format)                       users#create
                  login GET    /login(.:format)                        sessions#new
                        POST   /login(.:format)                        sessions#create
                 logout DELETE /logout(.:format)                       sessions#destroy
                  users GET    /users(.:format)                        users#index
                        POST   /users(.:format)                        users#create
               new_user GET    /users/new(.:format)                    users#new
              edit_user GET    /users/:id/edit(.:format)               users#edit
                   user GET    /users/:id(.:format)                    users#show
                        PATCH  /users/:id(.:format)                    users#update
                        PUT    /users/:id(.:format)                    users#update
                        DELETE /users/:id(.:format)                    users#destroy
                        PATCH  /users/:id/edit(.:format)               users#update
edit_account_activation GET    /account_activations/:id/edit(.:format) account_activations#edit
$ show-routes
                 Prefix Verb   URI Pattern                             Controller#Action
                   root GET    /                                       static_pages#home
                   help GET    /help(.:format)                         static_pages#help
                  about GET    /about(.:format)                        static_pages#about
                contact GET    /contact(.:format)                      static_pages#contact
                 signup GET    /signup(.:format)                       users#new
                        POST   /signup(.:format)                       users#create
                  login GET    /login(.:format)                        sessions#new
                        POST   /login(.:format)                        sessions#create
                 logout DELETE /logout(.:format)                       sessions#destroy
                  users GET    /users(.:format)                        users#index
                        POST   /users(.:format)                        users#create
               new_user GET    /users/new(.:format)                    users#new
              edit_user GET    /users/:id/edit(.:format)               users#edit
                   user GET    /users/:id(.:format)                    users#show
                        PATCH  /users/:id(.:format)                    users#update
                        PUT    /users/:id(.:format)                    users#update
                        DELETE /users/:id(.:format)                    users#destroy
                        PATCH  /users/:id/edit(.:format)               users#update
    account_activations GET    /account_activations(.:format)          account_activations#index
                        POST   /account_activations(.:format)          account_activations#create
new_account_activation GET    /account_activations/new(.:format)      account_activations#new
edit_account_activation GET    /account_activations/:id/edit(.:format) account_activations#edit
     account_activation GET    /account_activations/:id(.:format)      account_activations#show
                        PATCH  /account_activations/:id(.:format)      account_activations#update
                        PUT    /account_activations/:id(.:format)      account_activations#update
                        DELETE /account_activations/:id(.:format)      account_activations#destroy

ユーザーを有効にしたときの日時も念のために記録できるように、変更を加える。

$ bundle exec rails generate migration add_activation_to_users
Running via Spring preloader in process 81358
      invoke  active_record
      create    db/migrate/20170720012315_add_activation_to_users.rb
class AddActivationToUsers < ActiveRecord::Migration[5.1]
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean, default: false
    add_column :users, :activated_at, :datetime
  end
end
$ bundle exec rails db:migrate
== 20170720012315 AddActivationToUsers: migrating =============================
-- add_column(:users, :activation_digest, :string)
   -> 0.0643s
-- add_column(:users, :activated, :boolean, {:default=>false})
   -> 0.0548s
-- add_column(:users, :activated_at, :datetime)
   -> 0.0473s
== 20170720012315 AddActivationToUsers: migrated (0.1670s) ====================

Userモデルにアカウント有効化のコードを追加するために、models/user.rbを修正する。

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

サンプルユーザー生成のためのrbも修正

password = "Password"
User.create!(name:  "Example User",
              email: "example@email.com",
              password:              password,
              password_confirmation: password,
              admin: true,
              activated: true,
              activated_at: Time.zone.now)

99.times do |n|
  name  = Faker::Name.name
  email = "example#{n+1}@email.com"
  User.create!(name:  name,
              email: email,
              password:              password,
              password_confirmation: password,
              activated: true,
              activated_at: Time.zone.now)
end

前回と同じように、migrateのresetなどを行う。

# データをresetする。
$ bundle exec rails db:migrate:reset
# サンプルデータを作成
$ bundle exec rails db:seed

準備が出来てきたので、次のステップへ。

アカウント有効化のメール送信

アカウント有効化メールの送信に必要なコードを書いていく。

actoin mailerライブラリを使用する。

まず、そのためのコントローラーを作成する。

$ bundle exec rails generate mailer UserMailer account_activation password_reset
Running via Spring preloader in process 93826
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/account_activation.text.erb
      create    app/views/user_mailer/account_activation.html.erb
      create    app/views/user_mailer/password_reset.text.erb
      create    app/views/user_mailer/password_reset.html.erb
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb

このコマンドで、app/mailers/user_mailer.rb に、「account_activation」、「password_reset」の2つのメソッドが生成された。

また、自動で生成されたテンプレートなどは、それぞれ以下のようになっている。

User#account_activation

<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
<h1>User#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
class ApplicationMailer < ActionMailer::Base
  default from: '<your heroku app>'
  layout 'mailer'
end
class UserMailer < ApplicationMailer

  def account_activation
    @greeting = "Hi"

    mail to: "to@example.org"
  end

  def password_reset
    @greeting = "Hi"

    mail to: "to@example.org"
  end
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
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) %>

送信メールのプレビュー

引き続き、修正していく

  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'localhost:3000'
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
# 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

これで、以下の4つがみれるようになった。

ユーザーのcreateアクションを更新

メイラーをアプリケーションで実際に使えるようにするために、ユーザー登録を行うcreateアクションに数行追加する。

  def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now #log_in @user
      flash[:success] = "Please check your email to activate your account."
      redirect_to root_url #@user
    else
      render 'new'
    end
  end

これで、登録後リダイレクトしたホームページにアカウント有効化確認のメッセージが表示されるようになった。

アカウントを有効化する

AccountActivationsコントローラのeditアクションを書いていく。

authenticated?メソッドの抽象化

  def authenticated?(remember_token)
    return false if remember_digest.nil?
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
  end
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end

書き直したメソッドを使用している箇所を書きなおす

  # 記憶トークン (cookie) に対応するユーザーを返す
  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

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

有効でないユーザーがログインすることのないようにする

class SessionsController < ApplicationController
  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

つづいて、Userモデルにユーザー有効化メソッドを追加する

class User < ApplicationRecord
.
.
.
  # アカウントを有効にする
  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

ユーザーモデルオブジェクトからメールを送信するようにする。

    def create
    @user = User.new(user_params)
    if @user.save
      @user.send_activation_email
      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

ユーザーモデルオブジェクト経由でアカウントを有効化する

class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
      user.activate
      # 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

…なんだか難しくなってきた。。

本番環境でのメール送信

herokuに、sendgrに、というアドオンを追加する。

 $ heroku addons:create sendgrid:starter
Creating sendgrid:starter on ⬢ witharea... free
Created sendgrid-elliptical-41675 as SENDGRID_PASSWORD, SENDGRID_USERNAME
Use heroku addons:docs sendgrid to view documentation

つづいて、production.rbを修正していく

  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
  }

ちなみに、SENDGRID_USERNAME、SENDGRID_PASSWORDは、それぞれ、以下のコマンドで取得する。

$ heroku config:get SENDGRID_USERNAME
$ heroku config:get SENDGRID_PASSWORD

その後、herokuにupする。

$ git push heroku master
$ heroku run rails db:migrate

ちなみに、herokuのDBをリセットするコマンドは、以下

$ heroku pg:reset DATABASE

コメント

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

内容に問題なければ、下記の「コメントを送信する」ボタンを押してください。


  1. KATOON.NET
  2. TRASH
  3. アカウントの有効化 | rails チュートリアル 17