railsチュートリアル12章 パスワード再設定 自分メモ

webあれこれ

慣れるまで流れがよくわからなくなるので自分用メモ。

テーブルにパスワード再設定用のカラムを追加

usersテーブルに【reset_digest】【reset_sent_at】を追加する。

リセットかどうか判定し、期限切れにするために時間を記憶するカラムをつくる。

$ rails generate migration add_reset_to_users reset_digest:string reset_sent_at:datetime

マイグレーションファイルでちゃんとadd_columになっている項目が正しいか確認する。

//テーブル構築
db:maigrate

rails console

#カラム名確認
User.column_names

PasswordResetsコントローラーの生成

$ rails generate controller PasswordResets new edit --no-test-framework

※--no-test-framework テストを作成しない

パスワード再設定用リソースを追加する

#config/routes.rb

resources :password_resets,     only: [:new, :create, :edit, :update]

ログインフォームに【パスワードを忘れてしまった】というリンクを作る。

最初のログイン画面に「forgot password」のボタンを追記。

#password_resets#new → app/views/sessions/new.html.erbへ繋がる
<%= link_to "(forgot password)", new_password_reset_path %>
<% provide(:title, "Forgot password") %>
<h1>Forgot password</h1>
<!---パスワード再設定メールを送る画面-->
<div class="row">
  <div class="col-md-6 col-md-offset-3">
<!--password_resets#createへ--->
    <%= form_with(url: password_resets_path, scope: :password_reset,
                    local: true) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.submit "Submit", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

createアクションでパスワード再設定

app/views/sessions/new.html.erbのフォームはpassword_resets#createに繋がる。

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] = "Email sent with password reset instructions"
      redirect_to root_url
    else
      flash.now[:danger] = "Email address not found"
      render 'new'
    end
  end

Userモデルにパスワード再設定用メソッドを追加する

パスワードをハッシュ化してからデータベースへ保存。
再設定のメールの送信。

attr_accessor :remember_token, :activation_token, :reset_token

  # パスワード再設定の属性を設定する
  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

パスワード再設定のメールとテンプレートを整える

メーラーの設定
app/mailers/user_mailer.rb

  def password_reset(user)
    @user = user
    mail to: user.email, subject: "Password reset"
  end

メールテンプレートに認証リンクをのせる

発送するメールに認証するリンクをいれる。

password_resets#editへのパスとなっている

※app/views/user_mailer/password_reset.text.erb
<%= edit_password_reset_url(@user.reset_token, email: @user.email) %>

※app/views/user_mailer/password_reset.html.erb
<%= link_to "Reset password", edit_password_reset_url(@user.reset_token,
                                                      email: @user.email) %>

パスワード再設定のプレビューメソッド

Railsのメールプレビュー機能でパスワード再設定のプレビューメソッドを作る。

以下のリンクで仮のプレヴューを見ることができる
http://(サーバーまたはlocalhost:3000)/rails/mailers/user_mailer/password_reset

test/mailers/previews/user_mailer_preview.rb

#テストで使用するもの
def password_reset
    user = User.first
    user.reset_token = User.new_token
    UserMailer.password_reset(user)
  end

送信メールのテスト

test/mailers/user_mailer_test.rb

#以下を追記する
user.reset_token = User.new_token

パスワード再設定用viweを作成

メールのリンク先からedit.html.erbへ
app/views/password_resets/edit.html.erb

<%= form_with(model: @user, url: password_reset_path(params[:id])

省略

#hidden_field_tag :email //emailを単体で使用する
<%= hidden_field_tag :email, @user.email %>

★パスワード再設定フォームのフォームヘルパー 注意
emailは再度表示してもらわなくてもいいように隠す

f.hidden_field :email, @user.email
→params[:user][:email] に保存されてしまう。

hidden_field_tag :email, @user.email
→params[:email]に保存

一個だけパラメータを他のアクションへ単体で渡したい時は、hidden_field_tagを使います。
hidden_field_tag は独立して利用できます。

editアクションを編集

PasswordResetsコントローラーのeditは空でOK
beforeアクションでeditアクションをする前に、ユーザー情報を得る、正しいユーザーか確認する。

  before_action :get_user,   only: [:edit, :update]
  before_action :valid_user, only: [:edit, :update]

省略

private

    def get_user
      @user = User.find_by(email: params[:email])
    end

    # 正しいユーザーかどうか確認する
    def valid_user
      unless (@user && @user.activated? &&
              @user.authenticated?(:reset, params[:id]))
        redirect_to root_url
      end
    end

パスワードを再発行する

app/views/password_resets/edit.html.erbのパスをアップデートアクションが発生するパスにしているので→password_resets#update

app/controllers/password_resets_controller.rb

  def update
    if params[:user][:password].empty?
      @user.errors.add(:password, :blank)
      render 'edit'
    elsif @user.update(user_params)
      log_in @user
      @user.update_attribute(:reset_digest, nil)
      flash[:success] = "Password has been reset."
      redirect_to @user
    else
      render 'edit'
    end
  end

パスワード再設定の期限機能をもたせる

app/models/user.rb

user   # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end
  
  private

パスワードリセットコントローラーの編集

userモデルで作ったpassword_reset_expired?を使う。

app/controllers/password_resets_controller.rb

before_action :check_expiration, only: [:edit, :update]

省略

private

省略

    # トークンが期限切れかどうか確認する
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end

最後の統合テストを行う

$ rails generate integration_test password_resets

タイトルとURLをコピーしました