LoginSignup
0
0

More than 5 years have passed since last update.

railsチュートリアル 第十一章

Last updated at Posted at 2019-03-25

はじめに

この記事ではrailsチュートリアルを進めていって、引っかかったところや、つまづいたところを引っ張り出して要約しています。

アカウントの有効化

ユーザー登録はすでに実装すみだが、よくある送られてきたメールアドレスのリンクをクリックすると登録完了というような処理を実装する。

1:ユーザーの初期状態は「有効化されていない」(unactivated) にしておく。
2:ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
3:有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。
4:ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5:ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。

というものとなる。

AccountActivationsリソース

セッション機能を使って、アカウントの有効化という作業を「リソース」としてモデル化する。

AccountActivationsコントローラ

AccountActivationsリソースを作るために、まずは AccountActivationsコントローラ を生成する。

$ rails generate controller AccountActivations

ここで今回定義するeditを記述できそうだが、記述してしまうとテストやビューができてしまうから記述はしない。

次に名前付きルートを扱えるようにするためにルーティングにアカウント有効化用のresources行を追加する。

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

AccountActivationのデータモデル

冒頭で少し説明したように、有効化のメールには一意の有効化トークンが必要となる。

今回もパスワード実装などの時と同じように仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するようにする。

まずは usersテーブル に3つの属性を新しく追加する。

$ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime

生成されたマイグレーションファイルの追加修正としては、activated属性に default:false を設定しておく。
(adminのように)

migrate/[timestamp]_add_activation_to_users.rb
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モデル にアカウント有効化のコードを追加すると以下のようになる。

app/models/user.rb
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メソッドのビューファイルを以下に示す。
テキストメール:

app/views/user_mailer/account_activation.text.erb
UserMailer#account_activation

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

HTMLメール:

app/views/user_mailer/account_activation.html.erb
<h1>UserMailer#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>

また生成された2つのメイラーはこちら。

app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
app/mailers/user_mailer.rb
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つのメイラーのテンプレートをカスタマイズして実際に使用できるものにしていく。

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

この修正により、user.emailにメール送信し、件名も追加することができた。

さらにメールにはカスタムの有効化リンクを追加する。

この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにするため、リンクにはメールアドレスとトークンを両方含めておく必要がある。

よって、2つのファイルは以下のようになる。

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) %>

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

あとはユーザー登録を行う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に変更する。

アカウント有効化

このようにして、メールができたので、今度はAccountActivationsコントローラeditアクション を定義していく。

authenticated?メソッドの抽象化

以前は有効化トークンとメールをそれぞれparams[:id]とparams[:email]で参照できた。
パスワードのモデルと記憶トークンで学んだことを元に、次のようなコードでユーザーを検索して認証することにする。

user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])

ただし、このメソッドは記憶トークン用なので今は正常に動作しない。

よって、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

これにより、current_userメソッド の引数も修正する必要がある。

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

editアクション

authenticated? が更新されたので、editアクション を定義できるようになった。

既に有効になっているユーザーを誤って再度有効化しないために !user.activated? というコードを付け足す。

また、トークンが無効になるようなことは実際にはめったにないが、もしそうなった場合はルートURLにリダイレクトされる仕組みを書いている。

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.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アクションで有効化されたユーザーだけをログインさせるように追加修正する。

app/controllers/sessions_controller.rb

 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に情報を記入する必要がある。

config/environments/production.rb
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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0