【Rails】アカウントをアクティベート

- あらかじめDBには有効化されていない状態のレコードがある
- 顧客ID、メールアドレス、電話番号を入力すると有効化メールが送信される
- リンクを踏んで有効化、ユーザー情報更新、ログイン

という流れを実装したのでまとめます。

##前提
- customersテーブルには`activation_digest`、`activated_at`の2つのカラムが必要（無い場合は追加）

##Model
- `attr_accessor`で`activation_token`属性を有効化
- トークンとダイジェストを生成、代入するメソッドを作成
- 引数にトークンを取って、ダイジェストと比較するメソッドを作成

```app/models/customer.rb
class Customer < ApplicationRecord
  attr_accessor :activation_token
.
.
  # 引数のハッシュ値を返す
  def self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
    BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムトークン生成
  def self.new_token
    SecureRandom.urlsafe_base64
  end

  # 有効化トークンとダイジェストを作成及び代入
  def create_activation_digest
    self.activation_token  = self.class.new_token
    self.activation_digest = self.class.digest(activation_token)
  end

  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(activation_token)
    return false if activation_digest.nil?
    BCrypt::Password.new(activation_digest).is_password?(activation_token)
  end
end
```

##Mailer
インスタンス変数を引数にとり、メールアドレスやトークンを抽出して送信します。
なお、本番環境のメール送信部分は未完成です。サーバーのログからメールの文面を確認してます。
[【Rails】メールを送信する（ローカル）](https://qiita.com/d0ne1s/items/98bf70ee7fbf4bfb4cf9)

```app/mailers/customer_mailer.rb
class CustomerMailer < ApplicationMailer
  def account_activation(customer)
    @customer = customer
    mail to: customer.email, subject: 'アカウント有効化'
  end
end
```
```erb:app/views/customer_mailer/account_activation.text.erb
以下のアドレスにアクセスし、アカウントを有効化してください。
<%= edit_account_activation_url(id: @customer.activation_token, email: @customer.email) %>
```

##Controller
- **newアクション**：メールアドレス等からユーザー（アクティベートされてない）の検索フォームを表示する
- **createアクション**：該当するユーザーが見つかったら、トークンとダイジェストを生成し、メールを送信
- **sentアクション**：「メールを送信しました」という画面を表示する
- **editアクション**：トークンとダイジェストを比較し、一致したらユーザー登録フォームを表示
- **updateアクション**：ユーザー情報更新 + アカウント有効化

```app/controllers/account_activations_controller.rb
class AccountActivationsController < PublicController
  #メールアドレス等からユーザー（アクティベートされてない）の検索フォームを表示する
  def new
    @customer = Customer.new
  end

  #該当するユーザーが見つかったら、トークンとダイジェストを生成し、メールを送信
  def create
    customer_id = params[:customer][:customer_id]
    email = params[:customer][:email]
    phone = params[:customer][:phone]
    @customer = Customer.find_by('(customer_id = ?) AND (email = ?) AND (phone = ?)', customer_id, email, phone)
    unless @customer
      flash.now[:danger] = '該当するユーザーが見つかりませんでした'
      @customer = Customer.new
      render :new and return
    end
    @customer.create_activation_digest
    @customer.save
    CustomerMailer.account_activation(@customer).deliver_now
    redirect_to account_activations_sent_url(customer_id: @customer.id)
  end

  #「メールを送信しました」という画面を表示する
  def sent
    @customer = Customer.find_by(customer_id: params[:customer_id])
  end

  #トークンとダイジェストを比較し、一致したらユーザー登録フォームを表示
  def edit
    @customer = Customer.find_by(email: params[:email])
    unless @customer
      flash[:danger] = 'ユーザーが見つかりませんでした'
      redirect_to login_url and return
    end
    unless @customer.activated_at.nil?
      flash[:danger] = 'このアカウントはすでに有効化されています'
      redirect_to login_url and return
    end
    unless @customer&.authenticated?(params[:id])
      flash[:danger] = 'エラーが発生しました。もう一度やり直してください。'
      redirect_to login_url and return
    end
  end

  #ユーザー情報更新 + アカウント有効化
  def update
    @customer = Customer.find_by(customer_id: params[:id])
    unless activation_params[:password].length >= 8
      flash.now[:danger] = 'パスワードは8文字以上で入力してください'
      render :edit and return
    end
    if @customer.update(activation_params)
      @customer.update_attributes(activated_at: Time.zone.now)
      log_in @customer
      render 'account_activations/complete'
    else
      flash.now[:danger] = '入力内容に誤りがあります'
      render :edit
    end
  end

  private

  def activation_params
    params.require(:customer).permit(:customer_id, :email, :nickname, :phone, :password, :password_confirmation)
  end
end
```

##View
```erb:app/views/account_activations/new.html.erb
<%= form_with model: @customer, url: '/account_activations', local: true do |f| %>

 <%= f.text_field :customer_id %>
 <%= f.text_field :phone %>
 <%= f.text_field :email %>

 <%= f.submit value: '送信' %>
<% end %>
```
```erb:app/views/account_activations/sent.html.erb
<p>
ご入力いただいたメールアドレス(<%= @customer.email %>)に、確認用メールを送信しました。
メール本文に記載されているURLを開き、会員登録を完了させてください。
</p>
```
```erb:app/views/account_activations/edit.html.erb
<%= form_with model: @customer, url: "/account_activations/#{@customer.customer_id}", local: true do |f| %>
  <%= f.text_field :customer_id, readonly: true %>
  <%= f.text_field :phone, readonly: true %>
  <%= f.text_field :email, readonly: true %>
  <%= f.password_field :password, minlength: '8', required: 'true' %>
  <%= f.password_field :password_confirmation, minlength: '8', required: 'true' %>
  <%= f.text_field :nickname %>
  <%= f.submit '登録' %>
<% end %>
```
```erb:app/views/account_activations/complete.html.erb
<p class="box__toptxt">ご登録が完了いたしました。</p>
<%= link_to 'トップページ', root_path %>
```

##ルーティング
```config/routes.rb
resources :account_activations, only: %i[new create edit update]
get '/account_activations/sent', to: 'account_activations#sent'
```

##問題点
Modelでパスワードに入れる値に制限をかけたら、editアクションで`@customer`を取得することができなくなってしまったので、仕方なくコントローラーでバリデーションの処理を実装しました。