railsチュートリアル　第十一章

##はじめに
この記事ではrailsチュートリアルを進めていって、引っかかったところや、つまづいたところを引っ張り出して要約しています。

##アカウントの有効化

ユーザー登録はすでに実装すみだが、よくある送られてきたメールアドレスのリンクをクリックすると登録完了というような処理を実装する。

 >1:ユーザーの初期状態は「有効化されていない」(unactivated) にしておく。
2:ユーザー登録が行われたときに、有効化トークンと、それに対応する有効化ダイジェストを生成する。
3:有効化ダイジェストはデータベースに保存しておき、有効化トークンはメールアドレスと一緒に、ユーザーに送信する有効化用メールのリンクに仕込んでおく。
4:ユーザーがメールのリンクをクリックしたら、アプリケーションはメールアドレスをキーにしてユーザーを探し、データベース内に保存しておいた有効化ダイジェストと比較することでトークンを認証する。
5:ユーザーを認証できたら、ユーザーのステータスを「有効化されていない」から「有効化済み」(activated) に変更する。



というものとなる。

##AccountActivationsリソース
セッション機能を使って、アカウントの有効化という作業を「リソース」としてモデル化する。

###AccountActivationsコントローラ
AccountActivationsリソースを作るために、まずは ```AccountActivationsコントローラ``` を生成する。

```
$ rails generate controller AccountActivations
```
ここで今回定義するeditを記述できそうだが、記述してしまうとテストやビューができてしまうから記述はしない。

次に名前付きルートを扱えるようにするためにルーティングにアカウント有効化用のresources行を追加する。

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

##AccountActivationのデータモデル
冒頭で少し説明したように、有効化のメールには**一意の有効化トークン**が必要となる。



今回もパスワード実装などの時と同じように仮想的な属性を使ってハッシュ化した文字列をデータベースに保存するようにする。

まずは ```usersテーブル``` に3つの属性を新しく追加する。

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

生成されたマイグレーションファイルの追加修正としては、activated属性に ```default:false``` を設定しておく。
（adminのように）

```migrate/[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
```

マイグレーションを実行。

```
$ rails db:migrate
```
###Activationトークンのコールバック
**有効化トークンや有効化ダイジェストはユーザーオブジェクトが作成される前に作成しておく**必要がある。


メールアドレスをデータベースに保存する際はbefore_saveコールバックを利用し、オブジェクトが保存される直前、オブジェクトの作成時や更新時にそのコールバックが呼び出された。

今回は、**オブジェクトが作成されたときだけコールバックを呼び出したい**から、```before_createコールバック``` を使用する。

よって、```Userモデル``` にアカウント有効化のコードを追加すると以下のようになる。

 ```app/models/user.rb
class User < ApplicationRecord
  #attr_accessorにactivation_token追加
  attr_accessor :remember_token, :activation_token
  #それぞれbeforeコールバック
  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
```

有効化トークンは本質的に仮のものでなければならないから、このモデルの```attr_accessor```にもう１つ追加する。

また、```create_activation_digest```メソッドはUserモデル内でしか使用しないので、```private```キーワードの中に定義しておく。

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

データのモデル化が終わった。
次にアカウント有効化メールの送信に必要なコードを書いていく。

このメソッドでは**Action Mailerライブラリを使ってUserのメイラーを追加**する。

このメイラーはUsersコントローラのcreateアクションで有効化リンクをメール送信するために扱う事になる。


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

メイラーは、モデルやコントローラと同様に ```rails generate``` で生成できる。

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

この一行で今回必要となる ```account_activationメソッド``` と、第12章(パスワード再設定)で必要となる ```password_resetメソッド``` を生成することができた。

また、**上記の他にもメイラーごとに２つのビューファイルが作られる。**（テキストメール用とHTMLメール用）

account_activationメソッドのビューファイルを以下に示す。
テキストメール：

```app/views/user_mailer/account_activation.text.erb
UserMailer#account_activation

<%= @greeting %>, find me in app/views/user_mailer/account_activation.text.erb
```
HTMLメール：

```app/views/user_mailer/account_activation.html.erb
<h1>UserMailer#account_activation</h1>

<p>
  <%= @greeting %>, find me in app/views/user_mailer/account_activation.html.erb
</p>
``` 

また生成された２つのメイラーはこちら。

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

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.account_activation.subject
  #
  def account_activation
    @greeting = "Hi"

    mail to: "to@example.org"
  end

  # Subject can be set in your I18n file at config/locales/en.yml
  # with the following lookup:
  #
  #   en.user_mailer.password_reset.subject
  #
  def password_reset
    @greeting = "Hi"

    mail to: "to@example.org"
  end
end
```

この**２つにはデフォルトのfromアドレスや、宛先メールアドレスのような情報**がある。

この２つのメイラーのテンプレートをカスタマイズして実際に使用できるものにしていく。

```app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  #修正
  default from: "noreply@example.com"
  layout 'mailer'
end
```
```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.emailにメール送信し、件名も追加することができた。**

さらにメールにはカスタムの有効化リンクを追加する。

この後、Railsサーバーでユーザーをメールアドレスで検索して有効化トークンを認証できるようにするため、**リンクにはメールアドレスとトークンを両方含めておく**必要がある。

よって、２つのファイルは以下のようになる。

```app/views/user_mailer/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) %>
```
```app/views/user_mailer/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) %>
```

##ユーザーのcreateアクションを更新
あとはユーザー登録を行うcreateアクションに数行追加するだけで、メイラーをアプリケーションで実際に使うことができる。

```app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  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
  .
  .
  .
end
```

変更前は、ユーザーのプロフィールページにリダイレクトしていたが、アカウント有効化を実装するうえでは無意味な動作なので、 リダイレクト先をルートURLに変更する。

##アカウント有効化

このようにして、メールができたので、今度は**AccountActivationsコントローラ**の ```editアクション``` を定義していく。

###authenticated?メソッドの抽象化
以前は有効化トークンとメールをそれぞれparams[:id]とparams[:email]で参照できた。
パスワードのモデルと記憶トークンで学んだことを元に、次のようなコードでユーザーを検索して認証することにする。

```
user = User.find_by(email: params[:email])
if user && user.authenticated?(:activation, params[:id])
```

ただし、このメソッドは記憶トークン用なので今は正常に動作しない。

よって、**authenticatedメソッドを抽象化**する必要がある。

```app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end
  .
  .
  .
end
```

これにより、```current_userメソッド``` の引数も修正する必要がある。

```app/helpers/sessions_helper.rb
module SessionsHelper
  .
  .
  .
  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      #引数を更新
      if user && user.authenticated?(:remember, cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
  .
  .
  .
end
```

###editアクション

```authenticated?``` が更新されたので、```editアクション``` を定義できるようになった。

既に有効になっているユーザーを誤って再度有効化しないために ```!user.activated?``` というコードを付け足す。

また、トークンが無効になるようなことは実際にはめったにないが、もしそうなった場合はルートURLにリダイレクトされる仕組みを書いている。

 ```app/controllers/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
```

これにより、送られてきたurlをブラウザ上で開くと、
```"Account activated!"```
というメッセージを表示して、ログイン画面に映る。

ただ、まだユーザーログインの方法を変更していないから有効化はまだ機能してない。

よって、**createアクションで有効化されたユーザーだけをログインさせるように追加修正**する。

```app/controllers/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
```
これで、ユーザー有効化機能の大まかなところは実装できた。

あとはリファクタリングでよりソースを綺麗にすることができるが、ここでは省略。

##本番環境でのメール送信

ここまでの実装で、```development環境``` におけるアカウント有効化の流れは完成した。
次は、サンプルアプリケーションの設定を変更し、```production環境``` で実際にメールを送信できるようにする。

本番環境からメール送信するために、```「SendGrid」```というHerokuアドオンを利用してアカウントを検証する。
その中でも今回は```「starter tier」```というサービスを使うので以下のコマンドでアドオンをアプリケーションに追加する。

```
$ heroku addons:create sendgrid:starter
```

アプリケーションで ```SendGridアドオン``` を使うには、**production環境のSMTPに情報を記入する**必要がある。

```config/environments/production.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :smtp
  #それぞれのホストを入力
  host = '<your heroku app>.herokuapp.com'
  config.action_mailer.default_url_options = { host: host }
  ActionMailer::Base.smtp_settings = {
    :address        => 'smtp.sendgrid.net',
    :port           => '587',
    :authentication => :plain,
    :user_name      => ENV['SENDGRID_USERNAME'],
    :password       => ENV['SENDGRID_PASSWORD'],
    :domain         => 'heroku.com',
    :enable_starttls_auto => true
  }
  .
  .
  .
end
```

また、SendGridアカウントのuser_nameとpassword設定をコマンドで確認できるが上記のファイルはそうした変数はSendGridアドオンが自動的に設定されているから現時点で修正する必要はない。
確認するコマンドを以下に示しておく。

```
$ heroku config:get SENDGRID_USERNAME
$ heroku config:get SENDGRID_PASSWORD
```


##さいごに
第１１章では、実際にチュートリアルを進める中で一番時間をかけてしまった、、、
メールが届かないなどのトラブルがあったが、時間をかけて焦らず理解していこうと思う。

次↓
https://qiita.com/jonnyjonnyj1397/items/a2c0e3f87113854e2e30