Rails tutorial 11章　メール有効化

#はじめに
Railsチュートリアルを**自分が理解しやすい表現に置き換える目的**で利用しています。　手順を示す記事ではありません

#目的
新規登録のユーザーに確認用メールを送り、有効化する

##AccountActivationsソリース
注意
アカウントの有効化ステータスを変更するには本来**PATCHリクエストのupdateアクション**になるが、今回は**Mailer**を使うので、有効化リンクをメールで送信することになる
つまり、**ユーザーがリンクをクリックしブラウザに送られるアクション**は**Getリクエスト**になるので、eidtアクションを使う



##有効化トークン
有効化トークンには、記憶トークンのような仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するやり方をとる

```ruby
user.activation_token
```
仮想属性の有効化トークンにアクセス

```ruby
user.authenticated?(:activation, token)
```
ユーザーを認証する

ユーザーの有効をテストするために、activated属性を追加し論理値を取るようにする

```ruby
if user.activated?
```
###データモデルの追加

```ruby
rails generate migration add_activation_to_users \
>activation_digest:string activated:boolean activated_at:datetime
```
生成されたマイグレーションがこちら

```ruby
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属性のデフォルト値をfalseにしています(default: false)

###before_create
6章でメールアドレスの作成及び更新の際にコールバックが呼ばれるようUserモデルに**before_save**を定義しましたが、今回の場合では**作成されたときだけ**呼び出したいので**before_create**を定義する

```ruby:user.rb
before_create :create_activation_digest
.
.
private
  def create_activation_digest
  end
```
create_activation_digestはUserモデル内でしか使わない為、privateで隠蔽しておきます

create_activation_digestメソッドでは、トークンに対応するダイジェストを割り当てる

```ruby:user.rb
attr_accessor :remember_token, :activation_token
.
.
private
  def create_activation_digest
    salf.activation_token = User.new_token
    self.activation_digest = User.digest(activation_token)
  end
```
attr_accessorに追加したのは、:remember_token同様、:activation_tokenも**仮のもの**でなくてはならないから（攻撃を回避するために必要)

##UserMailer
それでは有効化のメール送信に必要なActionMailerライブラリを使ってUserのメイラーを追加します

```ruby
rails generate mailer UserMailer account_activation password_reset
```
これでtext用のビューとHtml用のビュー、Applicationメイラー
Userメイラーが生成される
user.mailer内には**アカウント有効化**メソッド、**パスワード再設定**メソッドが自動で追加される

```ruby:app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "from@example.com"
  layout 'mailer'
end
```

```ruby: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
```
@userのインスタンス変数はコントローラ同様、メイラーのビュー(text.erb , html.erb)でも利用できる
引数で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) %>
```
注目すべきはこの一行
これはアカウント有効化するためのURLが生成している

```ruby
edit_account_activation_url(@user.activation_token, email: @user.email)
```
今までを参考にすると

```ruby
edit_user_url(user)
```
はusers/:id/editというURLが生成される

```ruby
edit_account_activation_url(@user.activation_token, email: @user.email
```
ここでは、@user.activation_tokenを:idとみなしURLが生成され、その後にuserのアドレスを追加している
つまり、生成されるURLは**/account_activations/トークン/edit?メアド**となり
トークンはparams[:id]、メアドはparams[:email]で取得できる

:idの部分は実際にはnew_tokenのBase64で生成されているので

```ruby
account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com
```
となる

```ruby:user_mailer_preview.rb
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
```
user変数にデータベース上の最初のユーザーを代入する
####ユーザー登録を行う

```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
```
ユーザーを作成するアクション
UserMailer.account_activation(@user).deliver_now
では、user_mailer.rbで定義した

```ruby:user_mailer.rb
def account_activation(user)
  @user = user
  mail to: user.email, subject: "Account activation"
end
```
メソッドを呼び出し、引数にとった@userにメールを送信する
redirect先がroot_urlなのは作成後にログインしないようにするためである

##アカウントを有効化する
有効化するためには、アカウント有効化のダイジェストと渡されたトークンが一致するかチェックする

```ruby
user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])
```
ただし、現時点ではremember_token用なので、正常に動作しない

```ruby
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
  return false if remember_digest.nil?
  BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
```
###sendメソッド
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"
```
これを使えば、先ほどのauthenticated?もremember_tokenだけじゃなく他でも使えそう

```ruby:user.rb
# トークンがダイジェストと一致したらtrueを返す
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end
```
例えば、user.authencticated?(:activation, params[:id])としてeditアクションに組み込むと
第一引数の:activationはdigest = activation_digestとなり
第二引数のparams[:id]は有効化URLに含まれるトークンに値する
BCrypt::Password.new(digest).is_password?(token)
でdigestとトークンの比較を行う

```ruby: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
```
editアクションでは、
userをメアドで取得し、userが存在 -> activated == false -> activation_digest == activation_tokenであれば実行
user.activatedをtrueに更新
user.activated_atを現在時刻へ更新
userでログイン
userページへリダイレクト
という流れになる。ちなみｎ

```ruby
!user.activated?
```
は既に有効であるユーザーを再度有効化にしないために追加した論理値である

あとは有効でないユーザーがログインしないようにすればいい

```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?を追加