Rails 教程中 M.Hartl 的 Ruby 密码重置重定向测试失败

Password Reset Redirect Test Failing in M.Hartl's Ruby on Rails Tutorial

我已经在 Rails 教程中完成 M.Hartl 的 Ruby 一段时间了,即使出现错别字,我也能解决问题这导致我的集成测试失败等等,但我 运行 变成了一个我无法弄清楚的问题。我的带有正确参数的 get edit_password_reset_path() 正在重定向到主页 url,而不是应该的 password_resets/edit

失败的测试:

 FAIL["test_password_resets", PasswordResetsTest, 2015-11-02 15:50:02 +0000]
 test_password_resets#PasswordResetsTest (1446479402.08s)
        expecting <"password_resets/edit"> but rendering with <[]>
        test/integration/password_resets_test.rb:37:in `block in <class:PasswordResetsTest>'

这是我的 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'
    # Invalid email
    post password_resets_path, password_reset: { email: "" }
    assert_not flash.empty?
    assert_template 'password_resets/new'
    # Valid email
    post password_resets_path, 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
    # Password reset form
    user = assigns(:user)
    # Wrong email
    get edit_password_reset_path(user.reset_token, email: "")
    assert_redirected_to root_url
    # Inactive user
    user.toggle!(:activated)
    get edit_password_reset_path(user.reset_token, email: user.email)
    assert_redirected_to root_url
    user.toggle!(:activated)
    # Right email, wrong token
    get edit_password_reset_path('wrong token', email: user.email)
    assert_redirected_to root_url
    # Right email, right token
    get edit_password_reset_path(user.reset_token, email: user.email)

    assert_template 'password_resets/edit' # This is the assertion that fails

    assert_select "input[name=email][type=hidden][value=?]", user.email
    # Invalid password & confirmation
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "foobazzz",
                  password_confirmation: "barquuxz" }
    assert_select 'div#error_explanation'
    # Empty password
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "",
                  password_confirmation: "" }
    assert_not flash.empty?
    # Valid password & confirmation
    patch password_reset_path(user.reset_token),
          email: user.email,
          user: { password:              "foobazzz",
                  password_confirmation: "foobazzz" }
    assert is_logged_in?
    assert_not flash.empty?
    assert_redirected_to user
  end
end

这是我的 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]

  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, "can't be empty")
      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

    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.rb中的authenticated?方法:

# Returns true if the given token matches the digest.
def authenticated?(attribute, token)
  digest = send("#{attribute}_digest")
  return false if digest.nil?
  BCrypt::Password.new(digest).is_password?(token)
end

我不太确定还需要什么才能解决我的问题。我搜索了 Whosebug,第二次研究了代码,我什至还比较了 M.Hartl 的 github 存储库中的源代码。

Link to the appropriate branch on my repo.

我正在尝试完全遵循 this part of the tutorial.

问题出在 user.rb 模型中。

在我的模型中,我有以下代码:

def create_reset_digest
  self.reset_token = User.new_token
  update_attribute(:reset_digest,  User.digest(reset_digest))
  update_attribute(:reset_sent_at, Time.zone.now)
end

注意第一个 update_attribute() 调用的第二个参数 reset_digest。那实际上应该是 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