1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Rails チュートリアル第11章

Last updated at Posted at 2021-08-01

メール機能を使ってsignupを改善する
ブランチを変える

git checkout -b account-activation

AccountActivationsのフォルダを作成

rails generate controller AccountActivations

新しくマイグレーションファイルを作成 またカラム名とデータ型を指定する

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

editアクションへの名前付きルートが必要になるので
名前付きルートを扱えるようにルーティングにアカウント有効化用のresources行を追加
編集だけにする

Rails.application.routes.draw do
  root 'static_pages#home' 
.
.
.
  resources :account_activations, only: [:edit]
  #名前付きルートを扱えるようにするため
    #ルーティングにアカウント有効化用のresources行を追加
  # 編集だけ
end

指定したために作成したファイルのコードに名前とデータ型が書かれてある。
booleanではtrue,false,nilの3つがある。のでnilが出ないようにデフォルトで指定しておく。

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

migrateはmigrationファイルの内容をDBに反映する行為

rails db:migrate

Userモデルにアカウント有効化のコードを追加する

class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_mail
  before_create :create_activation_digest
.
.
.
  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

開発環境のサンプルユーザーを最初から有効にしておく

User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true,
             activated: true,
             activated_at: Time.zone.now)
             # admin: trueにすることで管理者にすることができる
             

# 追加のユーザーをまとめて生成する
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

テスト環境のユーザーも同じように書く。
データベースを初期化して、サンプルデータを再度生成し直し、変更を反映

$ rails db:migrate:reset
$ rails db:seed

送信メールのテンプレート

メイラーは、モデルやコントローラと同様にrails generateで生成
アカウント有効化メールの送信に必要なコードを追加のため
account_activation,password_resetはアクション名
UserMailerはコントローラ名という感じ

rails generate mailer UserMailer account_activation password_reset

生成されたApplicationメイラーのデフォルト値を更新した。

class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  # アドレスのデフォルト値を更新
  layout 'mailer'
  # layout コントローラーごとに各ページ共通の
    #ビューファイルを指定できるメソッド
  # mailerのページを指定する
end

Userメイラーを生成し、アカウント有効化リンクをメール送信する様にする

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
# 内容はわからん

送信メールのプレビュー

テンプレートの実際の表示を簡単に確認するためメールプレビューを使う。
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができる。
これを利用するには、アプリケーションのdevelopment環境の設定に手を加える必要がある。
development環境のメール設定

Rails.application.configure do
.
.
.
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false
  
  # '/account_activation/:token/edit?email=foobar@bar.com'
  host = '開発環境のurl'
  # example.comにはsample_appは無いから 
  # ここをコピペすると失敗します。
  # 自分の環境のホストに変えてください。
  # クラウドIDEの場合は以下をお使いください
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
.
.
.

end

アカウント有効化のプレビューメソッド

# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at /rails/mailers/user_mailer/account_activation
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    # activation_tokenは仮の属性でしかない
    # データベースのユーザーはこの値を実際には持っていません
    UserMailer.account_activation(user)
    # UserMailerクラスのメソッド
    # 引数はメールオブジェクト(メールのプレビュー)
    # 開発用データベースの最初のユーザーに定義して
      #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
# だいたいわからん

現在のメールの実装をテストする


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

users.rb

 def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    # attributeがremember,activation,resetだったりする
    return false if digest.nil?
    # remember_digestが空ならばfalseを返す
    # 先にreturnをすることでエラーを起こさないようにする
    # remember_tokenが空(nil)だから
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
    # データベースのremember_digestとトークンのremember_token
      #を照らし合わせる
  end

テストを行うとエラーが起こる

 Error:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil:
ArgumentError: wrong number of arguments (given 1, expected 2)
                                                                                    expected 引数は2つを期待しているが given 引数は1個しかない
    app/models/user.rb:49:in `authenticated?'
    app/helpers/sessions_helper.rb:20:in `current_user'
    test/helpers/sessions_helper_test.rb:15:in `block in <class:SessionsHelperTest>'

user_test.rb

test "authenticated? should return false for a user with nil digest" do
  # 認証する? ダイジェストが空(nil)のユーザーにはfalseを返す必要があります
    assert_not @user.authenticated?(:remember, '')
    # 空のデータベースは認証されないよね?
  end
Failure:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil [/home/ubuntu/environment/sample_app/test/helpers/sessions_helper_test.rb:15]:
--- expected
+++ actual
@@ -1 +1 @@
-#<User id: 762146111, name: "Michael Example", email: "michael@example.com", created_at: "2021-07-31 15:33:09", updated_at: "2021-07-31 15:33:13", password_digest: [FILTERED], remember_digest: "$2a$04$iDPLGzGFNJBBoRUPTNCKL.mdoxo7NQTKSPy3G8YKU/I...", admin: true, activation_digest: nil, activated: true, activated_at: "2021-07-31 15:33:09">
+nil

-#<User....の情報が来るべきだが +nil その情報が来ない というエラーらしい

editアクションを書く準備ができた
paramsハッシュで渡されたメールアドレスに対応するユーザーを認証
既に有効になっているユーザーを誤って再度有効化しないために必要
このコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまいます。

class AccountActivationsController < ApplicationController
  def edit
    ef 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

ここで使用していたインスタンスが消えてしまった。
また1から頑張りたい。

困ったこと

rails generate migration add_activation_to_usersしか入力せずにmigrationファイルを作成してしまった。

rails generate migration add_activation_to_users

このファイルを削除するために入力した。

rails destroy migration add_activation_to_users

見事に成功。
また新しくファイルを作成した。

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

これも成功した。

Error:
UsersSignupTest#test_valid_signup_information:
AbstractController::DoubleRenderError: Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
    app/controllers/users_controller.rb:54:in `create'
    test/integration/users_signup_test.rb:18:in `block (2 levels) in <class:UsersSignupTest>'
    test/integration/users_signup_test.rb:17:in `block in <class:UsersSignupTest>'</font>


rails test test/integration/users_signup_test.rb:15

なんと言っているわからないが、
user_controllerの54行目を確認してみたら

 log_in @user
 flash[:success] = "Welcome to the Sample App!" 
 redirect_to @user 

を削除していなかった。 動画通りになった。
鈍臭い...。

また困りごとが解決したら書きたいと思う。
努力したい。

1
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?