1
0

More than 1 year has passed since last update.

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

Posted at

app/mailers/user_mailer.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

パスワードをリセットするには、以下のリンクをクリックしてください:

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

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

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

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

.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>このリンクは2時間が期限です。</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

# 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

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

.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

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

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
    # 2時間まで
  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

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

最後のテストはわからないことばっかりだ。
頑張ろう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0