0
1

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 1 year has passed since last update.

railsチュートリアル第11章 有効化のテストとリファクタリング

Posted at

###有効化のテストとリファクタリング
アカウント有効化の統合テストを追加します

####ユーザー登録のテストにアカウント有効化を追加する
test/integration/users_signup_test.rb

require 'test_helper'

class UsersSignupTest < ActionDispatch::IntegrationTest
  
  def setup
    ActionMailer::Base.deliveries.clear
    # setupメソッドでこれを初期化しておかないと、並行して行われる
    # 他のテストでメールが配信されたときにエラーが発生してしまいます
  end
  
  test "invalid signup information" do
    get signup_path
    # signupのページにアクセス
    assert_no_difference 'User.count' do
    # ユーザー数が変わらないかをテストする
      post users_path, params: { user: { name:  "",
                                 # ハッシュのハッシュのようになっているように見える
                                         email: "user@invalid",
                                         password:              "foo",
                                         password_confirmation: "bar" } }
      # postリクエストを送信 フォーム送信をテストする
      # データの投稿した後 assert_no_difference で違いを比べるらしい。
    end
    assert_template 'users/new'
  end
  
  test "valid signup information with account activation" do
    get signup_path
    assert_difference 'User.count', 1 do
    # 
    # 第二引数でデータベースとの差異は1である。
      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
    # 配列deliveriesは変数
    
    user = assigns(:user)
    # assignsメソッドを使うと対応するアクション内のインスタンス変数に
    #   アクセスできるようになります
    # 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!
    # POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド
    assert_template 'users/show'
    # showアクションのビューを表示させる。
    assert is_logged_in?
    # ログイン中であるかを確認
    
  end
end
ubuntu:~/environment/sample_app (account-activation) $ rails t
Running via Spring preloader in process 4782
Started with run options --seed 57756

  44/44: [============================] 100% Time: 00:00:03, Time: 00:00:03

Finished in 3.78627s
44 tests, 183 assertions, 0 failures, 0 errors, 0 skips

####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
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  # before_actionメソッドを使って何らかの処理が実行される直前に
  # 特定のメソッドを実行する仕組み
  # ログインをさせる
  # :editと:updateアクションだけ
.
.
.
  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
      # リダイレクト先をプロフィールページからルートURLに変更
      # urlを指定して表示する
      # 保存成功したらurlを表示する
    else
      render 'new'
      # 保存に成功しなければnewアクションに移動する
      # 失敗したらまた戻る
    end
  end
.
.
.
end

####ユーザーモデルオブジェクト経由でアカウントを有効化する
app/controllers/account_activations_controller.rb

class AccountActivationsController < ApplicationController

  def edit
    user = User.find_by(email: params[:email])
    # paramsハッシュで渡されたメールアドレスに対応するユーザーを認証します
    if user && !user.activated? && user.authenticated?(:activation, params[:id])
    # 攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、
    #   本当のユーザーとしてログインできてしまいます。
      user.activate
      # リファクタリングされた
      # ユーザーを認証するには、ユーザーを認証してから
      #   activated_atタイムスタンプを更新する必要
      log_in user
      flash[:success] = "Account activated!"
      redirect_to user
    else
      flash[:danger] = "Invalid activation link"
      redirect_to root_url
      # トークンが無効になるようなことは実際にはめったにありませんが、
      #   もしそうなった場合はルートURLにリダイレクトされる仕組み
    end
  end
end

メソッドを使うことでリファクタリングされるのか。
リファクタリングとは、ソフトウェアの挙動を変えることなく、その内部構造を整理することです

###演習
1.
リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが1回で済むようになります(注意!update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります)。また、変更後にテストを実行し、 green になることも確認してください。

class User < ApplicationRecord
.
.
.
  # アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
    # update_attributeを2回呼び出さずに済む
    
  end
.
.
.
end

現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
  before_action :correct_user,   only: [:edit, :update]
  before_action :admin_user,     only: :destroy
  # before_actionメソッドを使って何らかの処理が実行される直前に
  # 特定のメソッドを実行する仕組み
  # ログインをさせる
  # :editと:updateアクションだけ
  
  def index
    @users = User.where(activated: true).paginate(page: params[:page])
    # allをpaginateメソッドに置き換えます
    # :pageパラメーターにはparams[:page]が使われていますが
    #   、これはwill_paginateによって自動的に生成されます
  end
  
  def show
    @user = User.find(params[:id])
    # データベースからユーザー情報を取り出す
    redirect_to root_url and return unless @user.activated?
    # ホーム画面にいき、何かを返す
  end
.
.
.
end

ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?