【Rails tutorial】「第11章　アカウントの有効化」の備忘録

#はじめに
Rails tutorialの`第11章　アカウントの有効化`ついて備忘録として、自分なりに整理しておく。
（※テストに関する記載はなし）

#準備
###アカウント有効化用のコントローラを作成する。

```terminal
$ rails generate controller AccountActivations
```

###routeを編集する。
後々、editの名前付きルートが必要になるので名前付きルートを使用できるようにする為に、ルートファイルにresourcesを追加する。

```ruby:routes.rb
Rails.application.routes.draw do
  -
  -
  resources :account_activations, only:[:edit]
end
```

###カラムを３つ加する。
アカウント有効化の処理に必要な３つのカラムをテーブルに追加する。
まずはマイグレーションファイルを作成する。

```terminal
$ rails g migration add_activation_to_users 
```

マイグレーションファイルを編集し、以下３つのカラムを追加し、`rails db:migrate`を実行する。
・activation_digest
・activated
・activated_at

```ruby:add_activation_to_users.rb
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean, default: false
    add_column :users, :activated_at, :datetime
  end
```

###仮想カラムを追加する。
第９章で`remember_token`カラムを追加した時のように、`attr_accessor`を使用して仮想カラムを追加する。

```ruby:user.rb
attr_accessor :remember_token, :activation_token
```

###コールバックを追加する。
`before_create`を使用して、ユーザーオブジェクトを作成（create）する前にデータベースに`activation_token`と`activation_digest`を作成するメソッドを追加しておく。

```ruby:user.rb
before_create :create_activation_digest

  #有効化トークンとダイジェストをデータベースに作成する。
  def create_activation_digest
    self.activation_token = User.new_token
    self.activation_digest = User.digest(activation_token)
  end
```


#メールの送信処理
以降はメールの送信処理について記載していく。


###メイラーを作成する
`rails generate`コマンドで作成可能。
rails g mailer [メイラー名] [メイラーのメソッド名（アクション名）].. 

作成されるファイルは以下の通り。
・メイラーファイル
・ビューのテンプレート２つ（テキスト形式とHTMl形式）
・テストファイル
・プレビューファイル


```terminal
$ rails g mailer UserMailer account_activation password_reset
      create  app/mailers/user_mailer.rb
      invoke  erb
      create    app/views/user_mailer
      create    app/views/user_mailer/account_activation.text.erb
      create    app/views/user_mailer/account_activation.html.erb
      create    app/views/user_mailer/password_reset.text.erb
      create    app/views/user_mailer/password_reset.html.erb
      invoke  test_unit
      create    test/mailers/user_mailer_test.rb
      create    test/mailers/previews/user_mailer_preview.rb

```

###アプリケーション全体の設定を編集する。
`app/mailers/application_mailer.rb`を開く。
メールの送信元アドレスを設定する。

```ruby:application_mailer.rb
aclass ApplicationMailer < ActionMailer::Base
  default from: 'noreply@example.com' #メールの送信元アドレスを指定。
  layout 'mailer'
end

```

###メールの送信処理を書き込む
`app/mailers/[メイラー名].rb`でメイラーファイルを開く。
メイラーファイルでは、`mailメソッド`を利用して、送信処理を作成するファイル。
それぞれのメソッドに対してmailメソッドを書き込み、送信処理を加えていく。

mailメソッドで利用可能な主なオプションは以下の通り。
from：送信元メールアドレス
subject：メールの件名
to：メールの送信先アドレス
etc...

```ruby:app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  def account_activation(user)
    @user = user   #インスタンス変数を作成
    mail(
      to: user.email,
      subject: "Account Activation"
    ) 
  end
end
```

###ビューのテンプレートを編集する。
送信されるメールのビューを編集していく。（textファイルとHTMLファイルの両方）
ビューには、アカウントを有効化する為に必要なURLを書き込む。


```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(@user.activation_token, email: @user.email) %>
```
プレビューで確認すると以下のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900807/a7bd2119-9aec-b6aa-8b48-19792390b38a.png)

```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(@user.activation_token, email: @user.email) %>
```
プレビューで確認すると以下のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/900807/1b63760e-b6a8-fbc5-c8d2-5309123b8606.png)


テンプレートを作成したら、プレビューで確認することができる。

###Createアクションを編集する
次は`users_controller.rb`ファイルの `Createアクション`を書き換えていく。

[変更前]

```ruby:users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end
```
[変更後]
・UserMailer.account_activation(@user).deliver_now #deliver_now
↑`UserMailer`の`account_activationメソッド`を介して、ユーザーにメールを送信させる　（deliver_nowは今すぐメールを送信したい場合に使用する）

・この時点でログインは完了していないので、セッションを持たせてはいけない為`log_in @user`は削除し、リダイレクト先もユーザーの詳細ページではなくrootページに変更しておく。

```ruby:users_controller.rb
  def create
    @user = User.new(user_params)
    if @user.save
      UserMailer.account_activation(@user).deliver_now 
      flash[:info] = "アカウントを有効化する為にメールを確認してください"
      redirect_to root_url
    else
      render "new"
    end
  end
```

###autheticateメソッドを編集する。
次に`user.rb`に記載していた`authenticatedメソッド`を書き換えていく。
アカウント有効化の処理の中で、トークンとダイジェストを比較する処理が発生する。この処理は「第９章　発展的なログイン」で既に行っている（`remember_token`と`remember_digest`を比較するメソッド）。
しかし、９章の時に書いたコードでは、`remember_token`と`remember_digest`しか比較をしないので、`activation_token`と`activation_digest`も比較できるようにコードを書き換える必要がある。

その為に`sendメソッド`を使用する。

```terminal
#sendメソッドの挙動
>> 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メソッド`を書き換えていく。

[変更前]

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

[変更後]
・`sendメソッド`を使用して、`remember`と`activation`の両方を入れられるようなコードにする。

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

###current_userメソッドを書き換える。
`authenticated?メソッド`を書き換えたことによって、`authenticated?メソッド`を使用していたコードを書き換える必要がある。
[変更前]

```ruby:sessions_helper.rb
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?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end
```

[変更後]
・`authenticated?メソッド`の（）内の部分を書き換える。
if user && user.authenticated?(cookies[:remember_token])
↓
if user && user.authenticated?(:remember, cookies[:remember_token])

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

###account__activations_controller.rbにeditアクションを追加する。
続いて、`account__activationsコントローラー`に`editアクション`を追加する。
まずアカウント有効化の核となるユーザー認証（先ほど編集したauthenticateメソッド）を行っていく。
また、`activated`カラムの論理値が`false`であることもここで確認する。

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

確認ができたら`activated`カラムと`activated_at`カラムの情報を更新し、ユーザーに`session`持たしアカウントを有効化させる。
そして、ユーザーの詳細ページにリダイレクトさせる。

```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)
      redirect_to user_url(@user)
      flash[:success] = "アカウントが有効になりました！"
    else
      flash[:danger] = "無効なリンクです。アカウントが有効化できません。"
      redirect_to root_url
    end
  end
end
```

###sessions_controllerのcreateアクションを編集する。
アカウントの`activated`カラムの論理値がtrueになっている場合のコードを追加する必要があります。

```
if @user.activated?
```

[変更前]

```ruby
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      if params[:session][:remember_me] == '1'
          remember(@user)
        else
          forget(@user)
        end
        redirect_to user_url(@user)
    else
      flash.now[:danger] = 'メールとパスワードの組み合わせが無効です。'
      render 'new'
    end
  end
```

[変更後]

```ruby
  def create
    @user = User.find_by(email: params[:session][:email].downcase)
    if @user && @user.authenticate(params[:session][:password])
      if @user.activated?
        log_in(@user)
        if params[:session][:remember_me] == '1'
          remember(@user)
        else
          forget(@user)
        end
        redirect_to user_url(@user)
      else
        flash[:warning] = "アカウントが有効化されていません。アカウント有効化についてはメールをご確認ください。"
        redirect_to root_url
      end
    else
      flash.now[:danger] = "メールとパスワードの組み合わせが無効です。"
      render "new"
    end
  end
```

#最後に
作成したコードをいくつかリファクタリングして終わり。
（リファクタリングのコードの内容は省略する。）



#参考文献
Ruby on Rails チュートリアル
第１１章　アカウント有効化
https://railstutorial.jp/chapters/account_activation?version=6.0#cha-account_activation