I've finished chapter 10 of Michael Hartl's Rails Tutorial, and I'm trying to add the password reset ability (part of chapter 10) to a new app that I'm making. But when I rake, I get an error:
ERROR["test_password_resets", PasswordResetsTest, 2015-07-07 15:21:45 -0700]
test_password_resets#PasswordResetsTest (1436307705.90s)
ActionView::Template::Error: ActionView::Template::Error: First argument in form cannot contain nil or be empty
app/views/password_resets/edit.html.erb:8:in `_app_views_password_resets_edit_html_erb___1321587905929812469_70306702568520'
test/integration/password_resets_test.rb:25:in `block in <class:PasswordResetsTest>'
app/views/password_resets/edit.html.erb:8:in `_app_views_password_resets_edit_html_erb___1321587905929812469_70306702568520'
test/integration/password_resets_test.rb:25:in `block in <class:PasswordResetsTest>'
Here is the edit.html.erb (where I'm having the problem):
<% provide(:title, "Reset your password") %>
<div class = "center jumbotron white">
<h2>Reset Your Password</h2>
<p class = "center">Choose your new password. Be wise about it and don't choose something you'll forget. </p>
<div class="row">
<div class="col-md-6 col-md-offset-3 top-medium-margins">
<%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
<%= render 'shared/error_messages' %>
<%= hidden_field_tag :email, @user.email %>
<%= f.password_field :password, class: 'form-control', placeholder: 'New password' %>
<%= f.password_field :password_confirmation, class: 'form-control', placeholder: 'Confirm your new password' %>
<%= f.submit "Update Password", class: "center btn btn-default green-hover top-small-margins" %>
<% end %>
</div>
</div>
</div>
Here is my password resets controller:
class PasswordResetsController < ApplicationController
before_action :get_user, only: [:edit, :update]
before_action :valid_user, only: [:edit, :update]
before_action :check_expiration, only: [:edit, :update]
before_action :logged_in_change_links, only: [:new, :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] = "An email was sent to " + @user.email + " with password reset instructions."
redirect_to login_url
else
flash.now[:danger] = "Hmm. We don't recognize that email. Make sure you signed up with this email."
render 'new'
end
end
def edit
end
def update
if params[:user][:password].empty?
flash.now[:danger] = "Uh oh. Your password can't be empty."
render 'edit'
elsif @user.update_attributes(user_params)
log_in @user
flash[:success] = "Success! Your 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 FILTERS
#Forgot => Profile if logged in
def logged_in_change_links
unless not logged_in?
redirect_to current_user
end
end
def get_user
@user = User.find_by(email: params[:email])
puts "Got user!"
end
# Confirms a valid user.
def valid_user
if (@user && @user.authenticated?(:reset, params[:id]))
redirect_to root_url
puts("Either I don't exist or I'm not authenticated.")
end
end
# Checks expiration of reset token.
def check_expiration
if (@user && @user.password_reset_expired?)
flash[:danger] = "Uh oh! Your password reset has expired. Please request a new password reset."
redirect_to new_password_reset_url
end
end
end
And my user model:
class User < ActiveRecord::Base
attr_accessor :remember_token, :reset_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i #Set valid email regex to be used
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
# Returns the hash digest of the given string.
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end
# Returns a random token.
def User.new_token
SecureRandom.urlsafe_base64
end
# Remembers a user in the database for use in persistent sessions.
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
# 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
# Forgets a user.
def forget
update_attribute(:remember_digest, nil)
end
#PASSWORD RESET
def create_reset_digest
"I'm at the beginning of 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)
puts "I got to the end of create_reset_digest"
end
def send_password_reset_email
UserMailer.password_reset(self).deliver_now
end
# Returns true if a password reset has expired.
def password_reset_expired?
reset_sent_at < 2.hours.ago
end
end
And finally, the actual integration test that tests the password reset: 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 login_url
# Password reset form
user = assigns(:user)
# Wrong email
get edit_password_reset_path(user.reset_token, email: "")
assert_redirected_to login_url
# Right email, wrong token
get edit_password_reset_path('wrong token', email: user.email)
assert_redirected_to login_url
# Right email, right token
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
# Invalid password & confirmation
patch password_reset_path(user.reset_token),
email: user.email,
user: { password: "foobaz",
password_confirmation: "barquux" }
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?
assert_template 'password_resets/edit'
# Valid password & confirmation
patch password_reset_path(user.reset_token),
email: user.email,
user: { password: "foobaz",
password_confirmation: "foobaz" }
assert is_logged_in?
assert_not flash.empty?
assert_redirected_to user
end
end
I've googled a lot and know that this error is caused because there is no @user instance variable in the edit method from the password controller. However, I know that there is one because a before filter is called before edit, which assigns @user to User.find_by(email: params[:email]).
Why is this happening? Let me know if I need to add something else.
Aucun commentaire:
Enregistrer un commentaire