アプリを作る パスワード再設定のメール送信

### app/mailers/user_mailer.rb
```rb
class UserMailer < ApplicationMailer

  def account_activation(user)
    @user = user
    mail to: user.email, subject: "アカウントを有効化"
  end
 
  def password_reset(user)
    @user = user
    # インスタンス変数に代入する
    mail to: user.email, subject: "パスワードをリセット"
   
  end
end
```
### app/views/user_mailer/password_reset.text.erb
```text
パスワードをリセットするには、以下のリンクをクリックしてください:

<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

このリンクは2時間で期限切れになります

パスワードのリセットをリクエストしなかった場合は、このメールを無視して、
パスワードはそのままです。
```

###　app/views/user_mailer/password_reset.html.erb
```html
.erb

<h1>パスワードをリセット</h1>

<p>To reset your password click the link below:</p>

<%= link_to "Reset password", edit_password_reset_url(@user.reset_token,
                                                      email: @user.email) %>
<p>このリンクは２時間が期限です。</p>

<p>
If you did not request your password to be reset, please ignore this email and
your password will stay as it is.
</p>
```
### test/mailers/previews/user_mailer_preview.rb
```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
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
    # パスワードリセットのメールを送信
  end

end
```
### test/mailers/user_mailer_test.rb
```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 "アカウントを有効化", 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
  
  test "password_reset" do
    user = users(:michael)
    user.reset_token = User.new_token
    mail = UserMailer.password_reset(user)
    assert_equal "パスワードをリセット", mail.subject
    assert_equal [user.email], mail.to
    assert_equal ["noreply@example.com"], mail.from
    assert_match user.reset_token,        mail.body.encoded
    assert_match CGI.escape(user.email),  mail.body.encoded
  end
end
```
### app/views/password_resets/edit.html.erb
```html
.erb

<% provide(:title, 'パスワードをリセット') %>
<h1>Reset password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, url: password_reset_path(params[:id]),
                  local: true) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
```
### app/controllers/password_resets_controller.rb
```rb
class PasswordResetsController < ApplicationController
  before_action :get_user,   only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]
  before_action :check_expiration, only: [:edit, :update]    # （1）への対応
  # 編集、更新をする前にget_user,valid_userメソッドが作動
  
  def new
  end
  
  def create
    @user = User.find_by(email: params[:password_reset][:email].downcase)
    if @user
      @user.create_reset_digest
      @user.send_password_reset_email
      flash[:info] = "パスワード再発行のためにメールを送信します。"
      redirect_to root_url
    else
      flash.now[:danger] = "Eメールアドレスが見つかりませんでした。"
      render 'new'
    end
  end
  
  def edit
  end
  
  def update
    if params[:user][:password].empty?                  # （3）への対応
    # ユーザーのパスワードが空ではないか？
      @user.errors.add(:password, :blank)
      # 例
        # > user.errors.add(:name, '文字数オーバー')
        # > user.errors.full_messages
          # => ["Name 文字数オーバー"] 
      # 多分　:blankにメッセージが入っていると思う
      render 'edit'
      # 表示させるviewファイルを指定して表示
    elsif @user.update(user_params)                    # （4）への対応
    # インスタンス変数が更新できれば
      log_in @user
      # ログインする
      flash[:success] = "Password has been reset."
      # メッセージを表示
      redirect_to @user
      # view の表示には直接は関係なく、新たな HttpRequest が発行されます。※GET のみ
    else
      render 'edit'                                     # （2）への対応
      # 編集ページを表示
    end
  end

  private
    
    def user_params
      params.require(:user).permit(:password, :password_confirmation)
      # RailsでDBを更新する際に、不要なパラメータを取り除く(必要なパラメータだけに絞り込む)ためのメソッドです。
      # ユーザーの:passwordと:password_confirmation属性だけ取り出す 
    end

    def get_user
      @user = User.find_by(email: params[:email])
      # dbからメアドを基に検索 インスタンス変数に代入
      # params[:email]のメールアドレスに対応するユーザーをこの変数に保存します。
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
      # インスタンス変数が有効性且つ有効化且つトークンの認証
      #  def authenticated?(attribute, token)
         # digest = send("#{attribute}_digest")
       # シンボルだけでいい
     # params[:id]はトークンを表すのかな？
        redirect_to root_url
        # 決められたコントローラーのアクション以外のアクションなどを実行させ、選択したビューファイルを表示させることができます。
      end
    end
    
    # トークンが期限切れかどうか確認する
    def check_expiration
      if @user.password_reset_expired?
      # インスタンス変数
        flash[:danger] = "Password reset has expired."
        # 失敗時のメッセージを表示
        redirect_to new_password_reset_url
        # パスワードリセット画面に移動する
        # コントローラ外の動き
      end
    end
end
```
### app/models/user.rb
```rb
class User < ApplicationRecord
# 継承させる
  attr_accessor :remember_token, :activation_token, :reset_token
  before_save   :downcase_email
  before_create :create_activation_digest
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false } 
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
  def User.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    # コストパラメータはテストでは最小にする
    BCrypt::Password.create(string, cost: cost)
  end
  
  # ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end
  
  # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end
  
  # トークンがダイジェストと一致したらtrueを返す
  def authenticated?(attribute, token)
    digest = send("#{attribute}_digest")
    return false if digest.nil?
    BCrypt::Password.new(digest).is_password?(token)
  end
  
  # ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
  
  def activate
  # # アカウントを有効にする
    update_attribute(:activated,    true)
    update_attribute(:activated_at, Time.zone.now)
  end

  # 有効化用のメールを送信する
  def send_activation_email
    UserMailer.account_activation(self).deliver_now
  end
  
  # パスワード再設定の属性を設定する
  def create_reset_digest
    self.reset_token = User.new_token
    update_attribute(:reset_digest,  User.digest(reset_token))
    update_attribute(:reset_sent_at, Time.zone.now)
  end

  # パスワード再設定のメールを送信する
  def send_password_reset_email
    UserMailer.password_reset(self).deliver_now
  end
  
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
    # ２時間まで
  end
  
  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
```
### test/integration/password_resets_test.rb
```rb
require 'test_helper'

class PasswordResetsTest < ActionDispatch::IntegrationTest
 
  def setup
    ActionMailer::Base.deliveries.clear
    # わからない
    @user = users(:michael)
    # テストユーザー
  end

  test "password resets" do
    get new_password_reset_path
    # パスワードリセット画面に移動することを要求
    assert_template 'password_resets/new'
    # 新しくメアド、パスワードを変更画面（ログイン画面）か確認
    assert_select 'input[name=?]', 'password_reset[email]'
    # inputの欄にemailに入力できるか？
    post password_resets_path, params: { password_reset: { email: "" } }
    # メールアドレスが無効
    # わからない
    assert_not flash.empty?
    # flashに中身があるか確認
    assert_template 'password_resets/new'
    # パスワード再発行ページが表示されているか？
    post password_resets_path,
         params: { password_reset: { email: @user.email } }
    # メールアドレスが有効
    assert_not_equal @user.reset_digest, @user.reload.reset_digest
    # reset_digestと再取得したreset_digestが違うか確認
    assert_equal 1, ActionMailer::Base.deliveries.size
　　　　　　　　# わからない
    assert_not flash.empty?
    # 中身はあるか確認
    assert_redirected_to root_url
    # ホーム画面に移動する
    user = assigns(:user)
    # パスワード再設定フォームのテスト
    get edit_password_reset_path(user.reset_token, email: "")
    # メールアドレスが無効
    assert_redirected_to root_url
    # 無効なユーザー
    user.toggle!(:activated)
    # 無効になっているユーザー
    get edit_password_reset_path(user.reset_token, email: user.email)
    # パスワード再発行のページに行くことを要求
    assert_redirected_to root_url
    # ホーム画面に移動する
    user.toggle!(:activated)
    get edit_password_reset_path('wrong token', email: user.email)
    # メールアドレスが有効で、トークンが無効
    assert_redirected_to root_url
    get edit_password_reset_path(user.reset_token, email: user.email)
    # メールアドレスもトークンも有効
    assert_template 'password_resets/edit'
    assert_select "input[name=email][type=hidden][value=?]", user.email
    # 無効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "barquux" } }
    assert_select 'div#error_explanation'
    # パスワードが空
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "",
                            password_confirmation: "" } }
    assert_select 'div#error_explanation'
    # 有効なパスワードとパスワード確認
    patch password_reset_path(user.reset_token),
          params: { email: user.email,
                    user: { password:              "foobaz",
                            password_confirmation: "foobaz" } }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
  end
end
end
```
最後のテストはわからないことばっかりだ。
頑張ろう。