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

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

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

```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
    # 
    # 第二引数でデータベースとの差異は１である。
      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

```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

```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

```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を２回呼び出していますが、これは各行で１回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを１回のupdate_columns呼び出しにまとめてみましょう。これでデータベースへの問い合わせが１回で済むようになります（注意！update_columnsは、モデルのコールバックやバリデーションが実行されない点がupdate_attributeと異なります）。また、変更後にテストを実行し、 green になることも確認してください。

```rb
class User < ApplicationRecord
.
.
.
  # アカウントを有効にする
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
    # update_attributeを2回呼び出さずに済む
    
  end
.
.
.
end
```
2.
現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。

```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 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
```
3.
ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。