Rails チュートリアル第１１章

メール機能を使ってsignupを改善する
ブランチを変える

```
git checkout -b account-activation
```
AccountActivationsのフォルダを作成

```rb
rails generate controller AccountActivations
```
新しくマイグレーションファイルを作成　またカラム名とデータ型を指定する

```rb
rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
```
editアクションへの名前付きルートが必要になるので
名前付きルートを扱えるようにルーティングにアカウント有効化用のresources行を追加
編集だけにする

```rb
Rails.application.routes.draw do
  root 'static_pages#home' 
.
.
.
  resources :account_activations, only: [:edit]
  #名前付きルートを扱えるようにするため
    #ルーティングにアカウント有効化用のresources行を追加
  # 編集だけ
end

```

指定したために作成したファイルのコードに名前とデータ型が書かれてある。
booleanではtrue,false,nilの３つがある。のでnilが出ないようにデフォルトで指定しておく。

```rb
class AddActivationToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :activation_digest, :string
    add_column :users, :activated, :boolean default: false
    add_column :users, :activated_at, :datetime
  end
end
```
migrateはmigrationファイルの内容をDBに反映する行為

```
rails db:migrate
```
Userモデルにアカウント有効化のコードを追加する

```rb
class User < ApplicationRecord
  attr_accessor :remember_token, :activation_token
  before_save   :downcase_mail
  before_create :create_activation_digest
.
.
.
  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

```
開発環境のサンプルユーザーを最初から有効にしておく

```
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin: true,
             activated: true,
             activated_at: Time.zone.now)
             # admin: trueにすることで管理者にすることができる
             

# 追加のユーザーをまとめて生成する
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
```
テスト環境のユーザーも同じように書く。
データベースを初期化して、サンプルデータを再度生成し直し、変更を反映

```
$ rails db:migrate:reset
$ rails db:seed
```
送信メールのテンプレート

メイラーは、モデルやコントローラと同様にrails generateで生成
アカウント有効化メールの送信に必要なコードを追加のため
account_activation,password_resetはアクション名
UserMailerはコントローラ名という感じ

```
rails generate mailer UserMailer account_activation password_reset
```
生成されたApplicationメイラーのデフォルト値を更新した。

```rb
class ApplicationMailer < ActionMailer::Base
  default from: "noreply@example.com"
  # アドレスのデフォルト値を更新
  layout 'mailer'
  # layout コントローラーごとに各ページ共通の
    #ビューファイルを指定できるメソッド
  # mailerのページを指定する
end
```
Userメイラーを生成し、アカウント有効化リンクをメール送信する様にする

```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
# 内容はわからん
```
####送信メールのプレビュー
テンプレートの実際の表示を簡単に確認するためメールプレビューを使う。
Railsでは、特殊なURLにアクセスするとメールのメッセージをその場でプレビューすることができる。
これを利用するには、アプリケーションのdevelopment環境の設定に手を加える必要がある。
development環境のメール設定

```rb
Rails.application.configure do
.
.
.
  # Don't care if the mailer can't send.
  config.action_mailer.raise_delivery_errors = false
  
  # '/account_activation/:token/edit?email=foobar@bar.com'
  host = '開発環境のurl'
  # example.comにはsample_appは無いから　
  # ここをコピペすると失敗します。
  # 自分の環境のホストに変えてください。
  # クラウドIDEの場合は以下をお使いください
  config.action_mailer.default_url_options = { host: host, protocol: 'https' }
.
.
.

end

```
アカウント有効化のプレビューメソッド

```rb
# Preview all emails at http://localhost:3000/rails/mailers/user_mailer
class UserMailerPreview < ActionMailer::Preview

  # Preview this email at /rails/mailers/user_mailer/account_activation
  def account_activation
    user = User.first
    user.activation_token = User.new_token
    # activation_tokenは仮の属性でしかない
    # データベースのユーザーはこの値を実際には持っていません
    UserMailer.account_activation(user)
    # UserMailerクラスのメソッド
    # 引数はメールオブジェクト（メールのプレビュー）
    # 開発用データベースの最初のユーザーに定義して
      #UserMailer.account_activationの引数として渡す
  end

  # Preview this email at http://localhost:3000/rails/mailers/user_mailer/password_reset
  def password_reset
    UserMailer.password_reset
  end

end
# だいたいわからん
```
現在のメールの実装をテストする

```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
```
users.rb

```rb
 def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    # attributeがremember,activation,resetだったりする
    return false if digest.nil?
    # remember_digestが空ならばfalseを返す
    # 先にreturnをすることでエラーを起こさないようにする
    # remember_tokenが空(nil)だから
    BCrypt::Password.new(remember_digest).is_password?(remember_token)
    # データベースのremember_digestとトークンのremember_token
      #を照らし合わせる
  end
```
テストを行うとエラーが起こる

```
 Error:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil:
ArgumentError: wrong number of arguments (given 1, expected 2)
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　expected 引数は２つを期待しているが　given 引数は１個しかない
    app/models/user.rb:49:in `authenticated?'
    app/helpers/sessions_helper.rb:20:in `current_user'
    test/helpers/sessions_helper_test.rb:15:in `block in <class:SessionsHelperTest>'

```
user_test.rb

```
test "authenticated? should return false for a user with nil digest" do
  # 認証する？　ダイジェストが空(nil)のユーザーにはfalseを返す必要があります
    assert_not @user.authenticated?(:remember, '')
    # 空のデータベースは認証されないよね？
  end
```

```
Failure:
SessionsHelperTest#test_current_user_returns_right_user_when_session_is_nil [/home/ubuntu/environment/sample_app/test/helpers/sessions_helper_test.rb:15]:
--- expected
+++ actual
@@ -1 +1 @@
-#<User id: 762146111, name: "Michael Example", email: "michael@example.com", created_at: "2021-07-31 15:33:09", updated_at: "2021-07-31 15:33:13", password_digest: [FILTERED], remember_digest: "$2a$04$iDPLGzGFNJBBoRUPTNCKL.mdoxo7NQTKSPy3G8YKU/I...", admin: true, activation_digest: nil, activated: true, activated_at: "2021-07-31 15:33:09">
+nil

-#<User....の情報が来るべきだが　+nil　その情報が来ない　というエラーらしい
```

editアクションを書く準備ができた
paramsハッシュで渡されたメールアドレスに対応するユーザーを認証
既に有効になっているユーザーを誤って再度有効化しないために必要
このコードがなければ、攻撃者がユーザーの有効化リンクを後から盗みだしてクリックするだけで、本当のユーザーとしてログインできてしまいます。

```rb
class AccountActivationsController < ApplicationController
  def edit
    ef 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

```

ここで使用していたインスタンスが消えてしまった。
また１から頑張りたい。

####困ったこと
rails generate migration add_activation_to_usersしか入力せずにmigrationファイルを作成してしまった。

```
rails generate migration add_activation_to_users
```
このファイルを削除するために入力した。

```
rails destroy migration add_activation_to_users
```
見事に成功。
また新しくファイルを作成した。

```
rails generate migration add_activation_to_users activation_digest:string activated:boolean activated_at:datetime
```
これも成功した。

```
Error:
UsersSignupTest#test_valid_signup_information:
AbstractController::DoubleRenderError: Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
    app/controllers/users_controller.rb:54:in `create'
    test/integration/users_signup_test.rb:18:in `block (2 levels) in <class:UsersSignupTest>'
    test/integration/users_signup_test.rb:17:in `block in <class:UsersSignupTest>'</font>


rails test test/integration/users_signup_test.rb:15
```
なんと言っているわからないが、
user_controllerの54行目を確認してみたら

```rb
 log_in @user
 flash[:success] = "Welcome to the Sample App!" 
 redirect_to @user 
```
を削除していなかった。　動画通りになった。
鈍臭い...。

また困りごとが解決したら書きたいと思う。
努力したい。