Railsチュートリアル11章まとめ(アカウントの有効化)

新規にユーザーを登録したとき、そのアドレス宛にアカウントを有効化するためのURL付のメールを送信し、そこにアクセスすることでアカウントを有効化する。

##AccountActivationsリソース

account_activationsコントローラーの準備

```
$ rails generate controller AccountActivations
```

ルートの設定

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

usersテーブルへカラムを追加。

```ruby
$ rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
```

それぞれの役割は、
activation_digest　有効化のためのトークン
activated　有効化済かどうか
ativated_at　有効化された日時


```ruby:[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
```

activatedのdefault値をfalseに指定。

```ruby
$ rails db:migrate
```

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

ここで重要なのはcreate_activation_digestメソッド。
このメソッドはbefore_createコールバックによって、createアクションが実行される前に実行される。
このメソッでの処理は、
①activation_token(attribute_accessorで定義した仮の属性)にランダムなトークンを代入。
②activation_digestカラムにトークンをダイジェスト化して代入。
このメソッドでは代入だけで保存する処理がされていないが、コールバックによってcreateアクションの前に実行されるため、アクション内で保存がされる。

そのため、この時点で新しくuserを作れば、activation_digestカラムに値の入ったuserが生成されうようになる。

ちなみに、トークンの生成メソッドとダイジェスト化のメソッドはこれ

```ruby:user.rb
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end
  
  def User.new_token
    SecureRandom.urlsafe_base64
  end
```

cookieを使った永続ログインやパスワードの再設定にもこのメソッドを使う。


##アカウント有効化のメール送信

次にメーラーの設定。

```ruby
$ rails generate mailer UserMailer account_activation password_reset
```

mailers/user_mailer.rbの中にアカウント有効化のためのメソッドとパスワード再設定のためのメソッドが自動で生成される。
しかしそのままでは使えないため編集する。
mailers/application_mailer.rbも生成されているが、こちらは別に触らなくても問題ない。
チュートリアル上では送信元のアドレスを変えているだけなので、そのままでも動作する。

```ruby: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を特定し、そのアドレスへメールを送信する。


```ruby: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) %>
```

```ruby: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) %>
```

上二つはメールのテンプレート。
text.erbとなっている方がテキストベース。
ここでアカウントを有効化するためのURLを生成している。
この部分。

```ruby
edit_account_activation_url(@user.activation_token, email: @user.email) 
```

例えば、editページへ変移したいときにはedit_user_path(user)と書き、これで/users/:id/editといったURLが生成される。
ここでは、@user.activation_tokenを:idとみなしてURLを生成し、その後にuserのアドレスをくっつけている。つまり、ここでは以下のようなURLを生成する。
/users/トークン/edit/メールアドレス
ここのトークンはparams[:id]で、メールアドレスはparams[:email]でそれぞれ取得できる。

次に、このメールのプレビューを確認する。

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

ここは特に説明の必要はなさそうなのでスキップ。
メソッドの上のURLにアクセスすればプレビューが表示される。開発環境によってlocal:3000(host名)を書きかえる。

```ruby:users_controller.rb
  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
```

新しいuserを生成するためのアクション。
ここで重要なのは、
UserMailer.account_activation(@user).deliver_now
の一行のみ。
user_mailer.rbで記述したaccount_activation(user)メソッドを呼び出し、引数に@userが入っている。この@userのメールアドレスに向けてメールを送信する、という処理になっている。
これでuserの新規作成は完了。
後はactivatedをdefaultのfalseからtrueに変えてやる処理だけ。

##アカウントを有効化する

アカウントを有効化するためには、activation_digestの値とactivation_tokenの値が一致するかを確認する。
ここで、ログイン永続化のため、remember_digestとremember_tokenが一致するか調べるために用いたauthenticater?メソッドを使うが、今のままでは使用できない。

```ruby:user.rb
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
```

このメソッドはremembet_tokenのためのメソッドなので、このままではactivation_tokenに流用することができない。
これを今回や、パスワード再設定の際にも使用できるように、下のように書き換える。

```ruby:user.rb
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end
```

このメソッドは user.authenticated?(:activation, params[:id])という形で実際にeditアクションの中に組み込む。
引数の:activationはauthenticated?メソッド内でattributeに代入され、activationd_digestとして振る舞うようになる。
第二引数のparams[:id]は、有効化のためのURLに含まれるトークンのこと。
 BCrypt::Password.new(digest).is_password?(token)
ここでダイジェストとトークンの比較を行っている。

authenticated?メソッド内で使われているsendメソッドが重要な役割を果たしているので、以下のコンソールでの動作を確認しておく。

```ruby
>> user = User.first
>> user.activation_digest
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send(:activation_digest)
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> user.send("activation_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
>> attribute = :activation
>> user.send("#{attribute}_digest")
=> "$2a$10$4e6TFzEJAVNyjLv8Q5u22ensMt28qEkx0roaZvtRcp6UZKRM6N9Ae"
```

```ruby:account_activations_controller.rb
 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
```

editアクション。
①URLに含まれているメールアドレスからuserを特定。
②userが存在し、かつ、user.activatedがfalseであり、かつ、activation_digestとURLに含まれているactivation_tokenが一致されば、処理を実行。
③activatedをtrueに更新。
④activated_atを現在時刻(更新された時刻)に更新
⑤ログイン
⑥users/showへ
といった流れ。

最後に、有効化が済んでいないuserのログインを拒否する処理を追加する。

```ruby: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
```
if user.activated?
この条件分岐を追加しただけ。trueの時だけログインできる。