我需要检查控制器方法中的一组条件。1) 它一团糟,2)甚至没有达到正确的重定向。
def password_set_submit
password_check = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*d{1,}){8,}.+$/
@user = User.find(session[:id])
if params[:password] && params[:password_confirmation] && params[:username] && params[:old_password]
if params[:password] == params[:password_confirmation] && params[:password] =~ password_check
# do some api stuff here
if @user.save
flash[:success] = 'Password updated.'
redirect_to login_path and return
end
end
if params[:password] != params[:password_confirmation]
flash[:error] = 'Passwords did not match.'
redirect_to password_set_path and return
end
if params[:password] == params[:password_confirmation] && params[:password] !~ password_check
flash[:error] = 'Passwords did not match password criteria.'
redirect_to password_set_path and return
end
end
else
flash[:error] = 'Please fill all inputs.'
redirect_to password_set_path and return
end
end
这需要执行以下操作:
1) 如果提交的参数少于四个,则重定向并显示"填充所有输入"
2) 如果密码和密码确认不匹配,重定向并显示"密码不匹配"
3) 如果密码和密码确认相互匹配但不符合条件,则重定向并显示"密码不符合条件"
4) 如果密码和密码确认相互匹配并且符合条件,则调用api并重定向到登录
我没有if/else的想法,我希望清理一下能帮助我正确地确定重定向。
Rails实现这一点的方法是使用模型验证。
class User < ActiveRecord::Base
validates :password, confirmation: true, presence: true# password must match password_confirmation
validates :password_confirmation, presence: true # a password confirmation must be set
end
如果我们试图在没有匹配pw/pw确认的情况下创建或更新用户,则操作将失败。
irb(main):006:0> @user = User.create(password: 'foo')
(1.5ms) begin transaction
(0.2ms) rollback transaction
=> #<User id: nil, password: "foo", password_confirmation: nil, created_at: nil, updated_at: nil>
irb(main):007:0> @user.errors.full_messages
=> ["Password confirmation can't be blank"]
irb(main):008:0>
然而
处理用户密码时,您应该NeverNever将其以纯文本形式存储在数据库中!
由于大多数用户重复使用通用密码,你也可能会损害他们的电子邮件、银行账户等。你可能会承担经济和法律责任,这可能会毁掉你的职业生涯。
答案是使用加密密码。由于这非常容易出错,Rails有一种叫做has_secure_password
的东西,它可以加密和验证密码。
您要做的第一件事是从users
数据库中删除password
和password_confirmation
列。
添加password_digest
列。然后将has_secure_password
添加到您的模型中。
class User < ActiveRecord::Base
PASSWORD_CHECK = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*d{1,}){8,}.+$/
has_secure_password
validates :password, format: PASSWORD_CHECK
end
这将自动添加密码验证、确认以及password
和password_confirmation
的getter和setter。
要检查旧密码是否正确,我们会这样做:
@user = User.find(session[:id]).authenticate(params[:old_password])
# user or nil
这是Rails的一个例子:
class UsersController
# We setup a callback that redirects to the login if the user is not logged in
before_action :authenticate_user!, only: [ :password_set_submit ]
def password_set_submit
# We don't want assign the the old_password to user.
unless @user.authenticate(params[:old_password])
# And we don't want to validate on the model level here
# so we add an error manually:
@user.errors.add(:old_password, 'The current password is not correct.')
end
if @user.update(update_password_params)
redirect_to login_path, notice: 'Password updated.'
else
# The user failed to update, so we want to render the form again.
render :password_set, alert: 'Password could not be updated.'
end
end
private
# Normally you would put this in your ApplicationController
def authenticate_user!
@user = User.find(session[:id])
unless @user
flash.alert('You must be signed in to perform this action.')
redirect_to login_path
end
end
# http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters
def update_password_params
params.require(:user).permit(:password, :password_confirmation)
end
end
注意到我们行动中的逻辑是如何简单得多的吗?要么用户被更新,我们重定向,要么它无效,我们重新呈现表单。
我们没有为每个错误创建一条闪烁消息,而是在表单上显示错误:
<%= form_for(@user, url: { action: :password_set_submit}, method: :patch) do |f| %>
<% if @user.errors.any? %>
<div id="error_explanation">
<h2>Your password could not be updated:</h2>
<ul>
<% @user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<%= f.label :password, 'New password' %>
<%= f.password_field_tag :password %>
</div>
<div class="row">
<%= f.label :password_confirmation %>
<%= f.password_field_tag :password_confirmation %>
</div>
<div class="row">
<p>Please provide your current password for confirmation</p>
<%= f.label :old_password, 'Current password' %>
<%= f.password_field_tag :old_password %>
</div>
<%= f.submit 'Update password' %>
<% end %>
我会从控制器中删除与此密码重置相关的所有代码,并将其放入自己的型号User::PasswordReset
:中
# in app/models/user/password_reset.rb
class User::PasswordReset
attr_reader :user, :error
PASSWORD_REGEXP = /^(?=.*[a-z]{1,})(?=.*[A-Z]{1,})(?=.*d{1,}){8,}.+$/
def initialize(user_id)
@user = User.find(user_id)
end
def update(parameters)
if parameters_valid?(parameters)
# do some api stuff here with `user` and `parameters[:password]`
else
false
end
end
private
def valid?
error.blank?
end
def parameters_valid?(parameters)
parameter_list_valid(parameters.keys) &&
password_valid(params[:password], params[:password_confirmation])
end
def parameter_list_valid(keys)
mandatory_keys = [:password, :password_confirmation, :username, :old_password]
unless mandatory_keys.all? { |key| keys.include?(key) }
@error = 'Please fill all inputs.'
end
valid?
end
def password_valid(password, confirmation)
if password != confirmation
@error = 'Passwords did not match.'
elsif password !~ PASSWORD_REGEXP
@error = 'Passwords did not match password criteria.'
end
valid?
end
end
这将允许将控制器的方法更改为更简单的方法,如
def password_set_submit
password_reset = User::PasswordReset.new(session[:id])
if password_reset.update(params)
flash[:success] = 'Password updated.'
redirect_to(login_path)
else
flash[:error] = password_reset.error
redirect_to(password_set_path)
end
end
一旦进行了重构,就应该更容易发现条件中的问题并扩展代码。