Ruby on Rails チュートリアル第11章をやってみて

#アカウントの有効化
■第11章
この章では、アカウントを有効化するステップを新規登録の途中に差し込むことで、本当にそのメールアドレスの持ち主なのかどうかを確認できるようにしてみる。

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

###11.1.1 AccountActivationsコントローラ
AccountActivationsコントローラを生成。

```rb
$ rails generate controller AccountActivations
```

ルーティングに`resources`行を追加。

```rb:routes.rb
resources :account_activations, only: [:edit]
```

###11.1.2 AccountActivationのデータモデル
ハッシュ化だったり、3つの属性が必要。

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

これで追加完了。`activated`属性のデフォルトの論理値を`false`にして、マイグレーションしておく。

Userモデルにアカウント有効化のコードを追加。

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

サンプルデータとfixtureも更新しておく。

`Time.zone.now`はRailsの組み込みヘルパーであり、サーバーのタイムゾーンに応じたタイムスタンプを返す。

```rb:seeds.rb
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin:     true,
             activated: true,
             activated_at: Time.zone.now)

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
              email: email,
              password:              password,
              password_confirmation: password,
              activated: true,
              activated_at: Time.zone.now)
end
```

fixtureのユーザーも更新。テストもGREENに。

##11.2 アカウント有効化のメール送信
アカウント有効化メールの送信に必要なコードを追加していく。

###11.2.1　送信メールのテンプレート
`rails generate`でメイラーの作成。

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

これで今回必要となる`account_activation`メソッドと`password_reset`メソッドが生成された。

生成されたテンプレートをカスタマイズして、実際に有効化メールで使えるようにする。`mail`に`subject`キーを引数として渡す。

```rb: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
```
###11.2.2 送信メールのプレビュー
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができるらしい。これを利用するために、development環境の設定に手を加える。

```rb:development.rb
Rails.application.configure do
  .
  .
  .
  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.delivery_method = :test
  host = 'example.com' # ここをコピペすると失敗します。自分の環境に合わせてください。
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
  .
  .
  .
end
```

hostには開発環境のurlで、https://を抜いたバージョンをいれとく。

次はUserメイラーのプレビューファイルを更新する。

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

無事プレビューが表示されました。
![スクリーンショット 2022-02-19 12.34.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2210462/03b482e5-5271-5e44-7aa5-29f05b82f4a2.png)


###11.2.3 送信メールのテスト
メールプレビューのテストも作成。

```rb:user_mailer_test.rb
require 'test_helper'

class UserMailerTest < ActionMailer::TestCase

  test "account_activation" do
    user = users(:michael)
    user.activation_token = User.new_token
    mail = UserMailer.account_activation(user)
    assert_equal "Account activation", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.name,               mail.body.encoded
    assert_match user.activation_token,   mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end
end
```

テストファイル内のドメイン設定に以下を追加。

```rb:
config.action_mailer.default_url_options = { host: 'example.com' }
```

ここも開発環境のURL。テストはGREENに。

###11.2.4　ユーザーのcreateアクションを更新
元々プロフィールページにリダイレクトしていたが、ルートURLに移動するように変更する。

ユーザー登録にアカウント有効化を追加。

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

このままではエラーになるので、統合のテストの一部をコメントアウトしておく。

##11.3 アカウントを有効化する
今度はAccountActivationsコントローラの`edit`アクションを書いていく。

###11.3.1 `authenticated?`メソッドの抽象化
プログラムでプログラムを作成することをメタプログラミングという。

`authenticated?`メソッドを書き換える。


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

このままだとテストは失敗する。`authenticated?`が古いままになっており、引数も2つではなくまだ1つのままだから。メソッドを書き換えたら無事てテストはGREENに。

###11.3.2 editアクションで有効化
editアクションでアカウントが作成されるようにして、一度リンクをクリックして有効化されたら有効化されないようにする。

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

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

###11.3.3 有効化のテストとリファクタリング
アカウント有効化の統合テストを追加する。正しい情報でユーザー登録を行った場合のテストは既にあるため、若干手を加える。

統合テストの中身を少し弄って、テストはGREENに。

`activate`メソッドを作成してユーザーの有効化属性を更新し、`send_activation_email`メソッドを作成するため、リファクタリングして終了。

##11.4　本番環境でのメール送信
ここまでの実装で、development環境におけるアカウント有効化の流れは完成した。次はサンプルアプリケーションの設定を変更し、production(実行)環境で実際にメールを送信してみる。


[クレカの登録はここを参考にしました。](https://wp.developapp.net/?p=5250)

なんかうまくできない。。
Heroku周りは本当にわからないまま時間が吸われていくので、一旦スキップします。

##感想
Railsはとにかく便利なモノ。書き方も正しいものがあるっぽい。とにかく難しかったです。。