質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.77%

Railsチュートリアル 第12章でPassword Resetフォームが開けない(password_resets/editが動作しない)

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 609

K2_Yoshiko

score 4

前提・実現したいこと

Railsチュートリアル第12章 3節「パスワードを再設定する」へ取り組む中で、調べてみても解決できないエラーに遭遇したので質問いたします。

12.3.1.1の演習中に、Railsサーバーのログからパスワードリセット用のメールリンクを探してアクセスしたところ、ルート画面に飛ばされてしまい、パスワードリセットのフォームが表示されない状況です。

teratailの同じエラーを出している記事も試してみましたがうまく行かず、12章や11章までさかのぼってコードをコピペしても状況は変わりませんでした。

発生している問題・エラーメッセージ

Railsサーバーのログ(再設定用メールのリンクにアクセスしたとき)

Started GET "/password_resets/75n959ghPFmrlJktckBrBg/edit?email=example%40railstutorial.org" for 49.104.15.211 at 2020-02-09 04:57:12 +0000
Cannot render console from 49.104.15.211! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by PasswordResetsController#edit as HTML
  Parameters: {"email"=>"example@railstutorial.org", "id"=>"75n959ghPFmrlJktckBrBg"}
  User Load (0.8ms)  SELECT  "users".* FROM "users" WHERE "users"."email" = ? LIMIT ?  [["email", "example@railstutorial.org"], ["LIMIT", 1]]
Redirected to https://93bfd3f26b434df4a4e3ab34c29fdd54.vfs.cloud9.us-east-2.amazonaws.com/
Filter chain halted as :valid_user rendered or redirected
Completed 302 Found in 78ms (ActiveRecord: 0.8ms)


Started GET "/" for 49.104.15.211 at 2020-02-09 04:57:12 +0000
Cannot render console from 49.104.15.211! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by StaticPagesController#home as HTML
  Rendering static_pages/home.html.erb within layouts/application
  Rendered static_pages/home.html.erb within layouts/application (2.8ms)
  Rendered layouts/_rails_default.html.erb (39.5ms)
  Rendered layouts/_shim.html.erb (0.4ms)
  Rendered layouts/_header.html.erb (0.7ms)
  Rendered layouts/_footer.html.erb (0.4ms)
Completed 200 OK in 49ms (Views: 48.5ms | ActiveRecord: 0.0ms)

12.3.3「パスワードの再設定をテストする」の実行結果

[1] guard(main)> 
05:08:07 - INFO - Run all
05:08:07 - INFO - Running: all tests
Running via Spring preloader in process 13101
Started with run options --seed 18780

 FAIL["test_password_resets", PasswordResetsTest, 1.5618210910006383]
 test_password_resets#PasswordResetsTest (1.56s)
        expecting <"password_resets/edit"> but rendering with <[]>
        test/integration/password_resets_test.rb:40:in `block in <class:PasswordResetsTest>'

  45/45: [=======================================================================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.84732s
45 tests, 200 assertions, 1 failures, 0 errors, 0 skips


/home/ec2-user/.rvm/gems/ruby-2.6.3/gems/guard-2.13.0/lib/guard/jobs/pry_wrapper.rb:279: warning: method Pry#input_array is deprecated. Use Pry#input_ring instead

該当のソースコード

PasswordResetsのコントローラー

class PasswordResetsController < ApplicationController

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

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

  def edit
  end

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

  private

    def user_params
      params.require(:user).permit(:password, :password_confirmation)
    end

    # beforeフィルター

    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

    # パスワード再設定の有効期限を確認
    def check_expiration
      if @user.password_reset_expired?
        flash[:danger] = "Password reset has expired."
        redirect_to new_password_reset_url
      end
    end
end

Userモデル

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\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 self.digest(string)
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
                                                  BCrypt::Engine.cost
    BCrypt::Password.create(string, cost: cost)
  end

  # ランダムなトークンを返す。cookiesに使用。
  def self.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_columns(activated: true, 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_columns( reset_digest: User.digest(reset_digest), 
                    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

    # emailを全て小文字化
    def downcase_email
      self.email.downcase!
    end

    # 有効化トークンとダイジェストを作成及び代入
    def create_activation_digest
      self.activation_token  = User.new_token
      self.activation_digest = User.digest(activation_token)
    end
end

パスワードリセットのテスト

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'
    # メールアドレスが無効
    post password_resets_path, params: { password_reset: { email: "" } }
    assert_not flash.empty?
    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
    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

試したこと

チュートリアルのコードをコピー&ペースト

teratailの他記事を実践

https://teratail.com/questions/227521

https://teratail.com/questions/201651?link=qa_related_pc

https://teratail.com/questions/239085
(未解決の同症状記事)


色々試していく中で、valid_user (password_resets_controller の beforeフィルタ)が悪さしているのではないかというところまでは分かったのですが、どう解決したらいいのかが分からずに詰まっております。

ご助言いただければとても嬉しいです。
よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

check解決した方法

0

Userモデルの以下の部分が間違って入力していました

誤り

# パスワード再設定の属性を設定
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns( reset_digest: User.digest(reset_digest), 
                    reset_sent_at: Time.zone.now)
  end

# パスワード再設定の属性を設定
  def create_reset_digest
    self.reset_token = User.new_token
    update_columns( reset_digest: User.digest(reset_token), 
                    reset_sent_at: Time.zone.now)
  end

コピー&ペーストに漏れがあっただけでした

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.77%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る