見出し画像

railsチュートリアル挑戦記 第11章 アカウントの有効化

railsチュートリアルをやりながらメモしたことをそのまま記述しています。

第11章アカウントの有効化

アカウントを有効化するステップを新規登録の途中に差し込むことで、
本当にそのメールアドレスの持ち主なのかどうかを確認できるようにする

①有効化トークンやダイジェストを関連付けておいた状態で
②有効化トークンを含めたリンクをユーザーにメールで送信し
③ユーザーがそのリンクをクリックすると有効化できるようにする

12章ではパスワードを再設定できる仕組みを実装する

railsの開発環境や本番環境からメールを実際に送信する方法についても学ぶ

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

ログイン・記憶トークン。アカウントの有効化・パスワードの再設定で似ている点

検索キー string digest authentication
email password password_digest authenticate(password)
id remember_token remember_digest authenticated?(:remember, token)
email activation_token activation_digest authenticated?(:activation, token)
email reset_token reset_digest authenticated?(:reset, token)

・11 1 AccountActivationsリソース

いつものようにトピックブランチ作成
git checkout -b account-activation

・11 1 1 AccountActivationsコントローラ

rails generate controller AccountActivations

ログ
----------------------------
ec2-user:~/environment/sample_app (account-activation) $ rails generate controller AccountActivations
Running via Spring preloader in process 6083
     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
----------------------------


有効化のメールには次のURLを含めることになる
edit_account_activation_url(activation_token, ...)

アカウント有効化に使うリソース (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
----------------------------

ここでテストスイートがGREENになることを確認

・11 1 2 AccountActivationのデータモデル

以下の3つが必要
activation_digest:string
activated:boolean
activated_at:datetime


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

上を立て続けに入力

ログ
----------------------------
ec2-user:~/environment/sample_app (account-activation) $ rails generate migration add_activation_to_users \
> activation_digest:string activated:boolean activated_at:datetime
Running via Spring preloader in process 6181
     invoke  active_record
     create    db/migrate/20200103153528_add_activation_to_users.rb
----------------------------

アカウント有効化用の属性とインデックスを追加するマイグレーション
db/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_create :create_activation_digest

Userモデルにアカウント有効化のコードを追加する green
app/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
----------------------------

サンプルデータとfixtureも更新しておく
Time.zone.nowはサーバーのタイムゾーンに応じたタイムスタンプを返す

サンプルユーザーを最初から有効にしておく
db/seeds.rb
----------------------------
User.create!(name:  "Example User",
            email: "example@railstutorial.org",
            password:              "foobar",
            password_confirmation: "foobar",
            admin:     true,
            activated: true,
            activated_at: Time.zone.now)
99.times do |n|
 name  = Faker::Name.name
 email = "example-#{n+1}@railstutorial.org"
 password = "password"
 User.create!(name:  name,
             email: email,
             password:              password,
             password_confirmation: password,
             activated: true,
             activated_at: Time.zone.now)
end
----------------------------
fixtureもTime.zone.nowを入れておく
fixtureのユーザーを有効にしておく
test/fixtures/users.yml
----------------------------
michael:
 name: Michael Example
 email: michael@example.com
 password_digest: <%= User.digest('password') %>
 admin: true
 activated: true
 activated_at: <%= Time.zone.now %>
archer:
 name: Sterling Archer
 email: duchess@example.gov
 password_digest: <%= User.digest('password') %>
 activated: true
 activated_at: <%= Time.zone.now %>
lana:
 name: Lana Kane
 email: hands@example.gov
 password_digest: <%= User.digest('password') %>
 activated: true
 activated_at: <%= Time.zone.now %>
malory:
 name: Malory Archer
 email: boss@example.gov
 password_digest: <%= User.digest('password') %>
 activated: true
 activated_at: <%= Time.zone.now %>
<% 30.times do |n| %>
user_<%= n %>:
 name:  <%= "User #{n}" %>
 email: <%= "user-#{n}@example.com" %>
 password_digest: <%= User.digest('password') %>
 activated: true
 activated_at: <%= Time.zone.now %>
<% end %>
----------------------------

データベースに反映

rails db:migrate:reset
rails db:seed

rails testでGREENになる

よし!順調!

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

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

・11 2 1 送信メールのテンプレート

rails generate mailerでメイラーを作れる

rails generate mailer UserMailer account_activation password_reset

ログ
----------------------------
ec2-user:~/environment/sample_app (account-activation) $ rails generate mailer UserMailer account_activation password_reset
Running via Spring preloader in process 6470
     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
ec2-user:~/environment/sample_app (account-activation) $ 
----------------------------


これで、今回必要となるaccount_activationメソッドとpassword_resetメソッドが生成された

アカウント有効化メイラーのテキストビュー (自動生成)
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>
----------------------------
生成されたApplicationメイラー
app/mailers/application_mailer.rb
----------------------------
class ApplicationMailer < ActionMailer::Base
 default from: "from@example.com"
 layout 'mailer'
end
----------------------------
生成されたUserメイラー
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
----------------------------

実際に有効化メールで使えるようにしていく
mailにsubjectキーを引数として渡しているが、これが件名に当たる

fromアドレスのデフォルト値を更新したアプリケーションメイラー
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
----------------------------


urlで@を扱うときは%40になる


アカウント有効化のテキストビュー
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) %>
----------------------------
アカウント有効化のHTMLビュー
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) %>
----------------------------

CGIで
CGI.escate('foo@example.com')
=> "foo%40example.com"

・11 2 2 送信メールのプレビュー


development環境のメール設定
config/environments/development.rb
----------------------------
Rails.application.configure do
 .
 .
 .
 config.action_mailer.raise_delivery_errors = true
 config.action_mailer.delivery_method = :test
 host = 'example.com' # ここはコピペしないこと!
 config.action_mailer.default_url_options = { host: host, protocol: 'https' }
 .
 .
 .
end
----------------------------

hostの部分は、

https://うんたらかんたらrtheast-1.amazonaws.com/

うんたらかんたらrtheast-1.amazonaws.com/
にしてコピペ

とりあえず開発環境のurlで、https://を抜いたバージョン


Userメイラープレビュー (自動生成)
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
   UserMailer.account_activation
 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
----------------------------
# 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
----------------------------

https://ごにょごにょ/rails/mailers/user_mailer/account_activation

と、

https://ごにょごにょ/rails/mailers/user_mailer/account_activation.txtで表示されるかな?たぶん

Dateがいくつか見てみる

見えたあああああああああああああああ!!!!!

感動!!!!!

Date:
Fri, 03 Jan 2020 16:00:38 +0000

・11 2 3 送信メールのテスト


Userメイラーのテスト (Railsによる自動生成)
test/mailers/user_mailer_test.rb
----------------------------
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
 test "account_activation" do
   mail = UserMailer.account_activation
   assert_equal "Account activation", mail.subject
   assert_equal ["to@example.org"], mail.to
   assert_equal ["from@example.com"], mail.from
   assert_match "Hi", mail.body.encoded
 end
 test "password_reset" do
   mail = UserMailer.password_reset
   assert_equal "Password reset", mail.subject
   assert_equal ["to@example.org"], mail.to
   assert_equal ["from@example.com"], mail.from
   assert_match "Hi", mail.body.encoded
 end
end
----------------------------
現在のメールの実装をテストする red
test/mailers/user_mailer_test.rb
----------------------------
require 'test_helper'
class UserMailerTest < ActionMailer::TestCase
 test "account_activation" do
   user = users(:michael)
   user.activation_token = User.new_token
   mail = UserMailer.account_activation(user)
   assert_equal "Account activation", mail.subject
   assert_equal [user.email], mail.to
   assert_equal ["noreply@example.com"], mail.from
   assert_match user.name,               mail.body.encoded
   assert_match user.activation_token,   mail.body.encoded
   assert_match CGI.escape(user.email),  mail.body.encoded
 end
end
----------------------------
テストのドメインホストを設定する
config/environments/test.rb
----------------------------
Rails.application.configure do
 .
 .
 .
 config.action_mailer.delivery_method = :test
 config.action_mailer.default_url_options = { host: 'example.com' }
 .
 .
 .
end
----------------------------
ユーザー登録にアカウント有効化を追加する red
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
----------------------------

rails testがgreenになることを確認

test/mailers/user_mailer_test.rbでCGI.escapeの部分をコメントアウトするとエラーになることを確認

ログ
----------------------------
rails test
Running via Spring preloader in process 7537
Started with run options --seed 31094
                                                        
FAIL["test_valid_signup_information", UsersSignupTest, 1.2003073949999816]
test_valid_signup_information#UsersSignupTest (1.20s)
       expecting <"users/show"> but rendering with <["user_mailer/account_activation", "layouts/mailer", "static_pages/home", "layouts/_rails_default", "layouts/_shim", "layouts/_header", "layouts/_footer", "layouts/application"]>
       test/integration/users_signup_test.rb:24:in `block in <class:UsersSignupTest>'
 44/44: [==========] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.61597s
44 tests, 175 assertions, 1 failures, 0 errors, 0 skips
ec2-user:~/environment/sample_app (account-activation) $  
----------------------------

先に進めすぎたみたい
ちょっと修正戻したらエラー取れた

・11 2 4 ユーザーのcreateアクションを更新

元々プロフィールページにリダイレクトしていたが、
ルートURLに移動するように変更する

ユーザー登録にアカウント有効化を追加する red
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
----------------------------

ここで、テストが失敗するようになるため、一時的にコメントアウトしておく(さっきのエラーはこれによるもの)

失敗するテストを一時的にコメントアウトする green
test/integration/users_signup_test.rb
----------------------------
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
 test "invalid signup information" do
   get signup_path
   assert_no_difference 'User.count' do
     post users_path, params: { user: { name:  "",
                                        email: "user@invalid",
                                        password:              "foo",
                                        password_confirmation: "bar" } }
   end
   assert_template 'users/new'
   assert_select 'div#error_explanation'
   assert_select 'div.field_with_errors'
 end
 test "valid signup information" do
   get signup_path
   assert_difference 'User.count', 1 do
     post users_path, params: { user: { name:  "Example User",
                                        email: "user@example.com",
                                        password:              "password",
                                        password_confirmation: "password" } }
   end
   follow_redirect!
   # assert_template 'users/show'
   # assert is_logged_in?
 end
end
----------------------------

ルートページに、メッセージが表示されるのを確認

サーバーログに表示されたアカウント有効化メールの例
----------------------------
UserMailer#account_activation: processed outbound mail in 292.4ms
Sent mail to michael@michaelhartl.com (47.3ms)
Date: Mon, 06 Jun 2016 20:17:41 +0000
From: noreply@example.com
To: michael@michaelhartl.com
Message-ID: <f2c9222494c7178e@mhartl-rails-tutorial-3045526.mail>
Subject: Account activation
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_5755da6513e89_f2c9222494c71639";
charset=UTF-8
Content-Transfer-Encoding: 7bit

----==_mimepart_5755da6513e89_f2c9222494c71639
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hi Michael Hartl,
Welcome to the Sample App! Click on the link below to activate your account:
https://rails-tutorial-mhartl.c9users.io/account_activations/
-L9kBsbIjmrqpJGB0TUKcA/edit?email=michael%40michaelhartl.com
----==_mimepart_5755da6513e89_f2c9222494c71639
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<!DOCTYPE html>
<html>
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <style>
     /* Email styles need to be inline */
   </style>
 </head>
 <body>
   <h1>Sample App</h1>
<p>Hi Michael Hartl,</p>
<p>
Welcome to the Sample App! Click on the link below to activate your account:
</p>
<a href="https://rails-tutorial-mhartl.c9users.io/account_activations/
-L9kBsbIjmrqpJGB0TUKcA/edit?email=michael%40michaelhartl.com">Activate</a>
 </body>
</html>
----==_mimepart_5755da6513e89_f2c9222494c71639--
images/figures/redirected_not_activated
----------------------------

・11 3 アカウントを有効化する

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

・11 3 1 authenticated?メソッドの抽象化

プログラムでプログラムを作成することをメタプログラミングという

抽象化されたauthenticated?メソッド red
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
----------------------------

rails testで失敗することを確認

ログ
----------------------------
rails test
Running via Spring preloader in process 8569
Started with run options --seed 15291
ERROR["test_authenticated?_should_return_false_for_a_user_with_nil_digest", UserTest, 0.8228407409997089]
test_authenticated?_should_return_false_for_a_user_with_nil_digest#UserTest (0.82s)
ArgumentError:         ArgumentError: wrong number of arguments (given 1, expected 2)
           app/models/user.rb:33:in `authenticated?'
           test/models/user_test.rb:75:in `block in <class:UserTest>'
ERROR["test_current_user_returns_nil_when_remember_digest_is_wrong", SessionsHelperTest, 1.5487051019999853]
test_current_user_returns_nil_when_remember_digest_is_wrong#SessionsHelperTest (1.55s)
ArgumentError:         ArgumentError: wrong number of arguments (given 1, expected 2)
           app/models/user.rb:33:in `authenticated?'
           app/helpers/sessions_helper.rb:25:in `current_user'
           test/helpers/sessions_helper_test.rb:17:in `block in <class:SessionsHelperTest>'
ERROR["test_current_user_returns_right_user_when_session_is_nil", SessionsHelperTest, 1.559482666999429]
test_current_user_returns_right_user_when_session_is_nil#SessionsHelperTest (1.56s)
ArgumentError:         ArgumentError: wrong number of arguments (given 1, expected 2)
           app/models/user.rb:33:in `authenticated?'
           app/helpers/sessions_helper.rb:25:in `current_user'
           test/helpers/sessions_helper_test.rb:11:in `block in <class:SessionsHelperTest>'
 44/44: [==========] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.64842s
44 tests, 172 assertions, 0 failures, 3 errors, 0 skips
ec2-user:~/environment/sample_app (account-activation) $  
----------------------------

失敗する理由は、authenticatedを使っている部分が古いコードのままなため

current_user内の抽象化したauthenticated?メソッド red
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
----------------------------
Userテスト内の抽象化したauthenticated?メソッド green
test/models/user_test.rb
----------------------------
require 'test_helper'
class UserTest < ActiveSupport::TestCase
 def setup
   @user = User.new(name: "Example User", email: "user@example.com",
                    password: "foobar", password_confirmation: "foobar")
 end
 .
 .
 .
 test "authenticated? should return false for a user with nil digest" do
   assert_not @user.authenticated?(:remember, '')
 end
end
----------------------------

rails testでGreenになることを確認

確認した!イイネ!!!


・11 3 2 editアクションで有効化

editアクションでアカウントが作成されるようにする

一度リンクをクリックして有効化されたら有効化されないようにする

アカウントを有効化するeditアクション
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
----------------------------
有効でないユーザーがログインすることのないようにする
app/controllers/sessions_controller.rb
----------------------------
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
----------------------------


・11 3 3 有効化のテストとリファクタリング

アカウント有効化の統合テストを追加する
正しい情報でユーザー登録を行った場合のテストは既にあるため、若干手を加える

ユーザー登録のテストにアカウント有効化を追加する green
test/integration/users_signup_test.rb
----------------------------
require 'test_helper'
class UsersSignupTest < ActionDispatch::IntegrationTest
 def setup
   ActionMailer::Base.deliveries.clear
 end
 test "invalid signup information" do
   get signup_path
   assert_no_difference 'User.count' do
     post users_path, params: { user: { name:  "",
                                        email: "user@invalid",
                                        password:              "foo",
                                        password_confirmation: "bar" } }
   end
   assert_template 'users/new'
   assert_select 'div#error_explanation'
   assert_select 'div.field_with_errors'
 end
   
 test "valid signup information with account activation" do
   get signup_path
   assert_difference 'User.count', 1 do
     post users_path, params: { user: { name:  "Example User",
                                        email: "user@example.com",
                                        password:              "password",
                                        password_confirmation: "password" } }
   end
   assert_equal 1, ActionMailer::Base.deliveries.size
   user = assigns(:user)
   assert_not user.activated?
   # 有効化していない状態でログインしてみる
   log_in_as(user)
   assert_not is_logged_in?
   # 有効化トークンが不正な場合
   get edit_account_activation_path("invalid token", email: user.email)
   assert_not is_logged_in?
   # トークンは正しいがメールアドレスが無効な場合
   get edit_account_activation_path(user.activation_token, email: 'wrong')
   assert_not is_logged_in?
   # 有効化トークンが正しい場合
   get edit_account_activation_path(user.activation_token, email: user.email)
   assert user.reload.activated?
   follow_redirect!
   assert_template 'users/show'
   assert is_logged_in?
 end
end
----------------------------

rails testでgreenになることを確認

ここも通っていくゥ!!

リファクタリングしていく

Userモデルにユーザー有効化メソッドを追加する
app/models/user.rb
----------------------------
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
----------------------------
ユーザーモデルオブジェクトからメールを送信する
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
----------------------------

rails testでgreenになること

通った!!!

update_columnsを使用するテンプレート
app/models/user.rb
----------------------------
class User < ApplicationRecord
 attr_accessor :remember_token, :activation_token
 before_save   :downcase_email
 before_create :create_activation_digest
 .
 .
 .
 # アカウントを有効にする
 def activate
   update_columns(activated: true, activated_at: Time.zone.now)
 end
 # 有効化用のメールを送信する
 def send_activation_email
   UserMailer.account_activation(self).deliver_now
 end
 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
----------------------------
有効なユーザーだけを表示するコードのテンプレート
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 @user.activated?
 end
 .
 .
 .
end
----------------------------


・11 4 本番環境でのメール送信

今回ここが激ムズかも知れない

まず、Herokuにクレジットカードの登録が必要
https://wp.developapp.net/?p=5250
を参考にした

すぐできた


ここも参考になるかも?
https://qiita.com/daichi_t/items/d34c9ca683d7dd8e77df

さあやるか・・・

下記は13章で必要らしい


これでユーザーネームとパスワードが表示できるらしい

そしていつものようにBitbucketとHerokuにプッシュ

git add -A
git commit -m "Add account activation"
git checkout master
git merge account-activation
rails test
git push

source <(curl -sL https://cdn.learnenough.com/heroku_install)
heroku create
git remote set-url heroku
heroku rename jun-killer-11

https://jun-killer-11.herokuapp.com/
ができた!

heroku maintenance:on


以下のhostに、アプリ名を入れる!

Railsのproduction環境でSendGridを使う設定
config/environments/production.rb
----------------------------
Rails.application.configure do
 .
 .
 .
 config.action_mailer.raise_delivery_errors = true
 config.action_mailer.delivery_method = :smtp
 host = '<ここ注意ね!自分の作ったURL!!>.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というHerokuアドオンを利用してアカウントを検証する必要がある
本チュートリアルではstarter tierを使う

heroku addons:create sendgrid:starter

失敗するようなら

heroku addons:add sendgrid:starter

を試してみる

今回は上のコマンドでいけた!

そして下記のコマンドで色々表示できた!
heroku config:get SENDGRID_USERNAME
heroku config:get SENDGRID_PASSWORD

git add -A
git commit -m "modify"

git push heroku master
heroku pg:reset DATABASE
jun-killer-11
heroku run rails db:migrate
heroku run rails db:seed
heroku restart
heroku maintenance:off

https://jun-killer-11.herokuapp.com

試しに新規登録したらめちゃくちゃ上手く行った!!!!
気持ちいい!!!!!

・11 5 最後に

次はパスワードの再設定機能

・11 5 1 本章のまとめ


アカウント有効化は、ActiveRecordオブジェクトではないが、セッションと同様にリソースでモデル化できる
Railsはメール送信で扱うActionMailerのアクションとビューを生成することができる
ActionMailerではテキストメールとHTMLメールの両方を利用できる
メイラーアクションで定義したインスタンス変数は、他のアクションやビューと同様に、メイラーのビューから参照できる
アカウントを有効化させるために、生成したトークンを使って一意のURLを作る
より安全なアカウント有効化のために、ハッシュ化したトークン(ダイジェスト)を使う
メイラーのテストと統合テストは、どちらもUserメイラーの振舞いを確認するのに有用
SendGridを使うと、production環境からメールを送信できる


この記事が気に入ったらサポートをしてみませんか?