[英]Rails controller params conditionals - how to do this cleanly
I'm need to check a bunch of conditions in a controller method. 我需要在控制器方法中检查一堆条件。 1) it's a mess and 2) it's not even hitting the right redirects.
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
This needs to do the following: 这需要执行以下操作:
1) If less than four params submitted, redirect and display 'Fill all inputs' 1)如果提交的参数少于四个,则重定向并显示“填写所有输入”
2) If password and password confirmation don't match each other, redirect and display 'Password did not match' 2)如果密码和密码确认不匹配,请重定向并显示“密码不匹配”
3) If password and password confirmation match each other but do not match criteria, redirect and display 'Passwords did not match criteria' 3)如果密码和密码确认彼此匹配但不符合标准,请重定向并显示“密码不符合标准”
4) If password and password confirmation match each other and match criteria, make api call and redirect to login 4)如果密码和密码确认彼此匹配并且符合条件,请进行api调用并重定向到登录
I'm out of if/else ideas and I hope cleaning this up will help me nail the redirects correctly. 我没有if / else的主意,希望清除此内容将有助于我正确定位重定向。
The Rails way to this is by using model validations. 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
If we try to create or update a user without a matching pw / pw confirmation the operation will fail. 如果我们尝试创建或更新没有匹配的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>
When dealing with user passwords you should NEVER NEVER NEVER store them in the database in plain text! 处理用户密码时, 永远不要以纯文本形式将它们存储在数据库中!
Since most users reuse a common password you might also be compromising their email, bank account etc. You could potentially be held financially and legally responsible and it can destroy your career. 由于大多数用户重复使用通用密码,因此您可能还会破坏他们的电子邮件,银行帐户等。您可能会承担财务和法律责任,并可能破坏您的职业生涯。
The answer is to use an encrypted password. 答案是使用加密的密码。 Since this is incredibly easy to get wrong Rails has something called
has_secure_password
which encrypts and validates passwords. 由于这很容易出错,所以Rails拥有一种称为
has_secure_password
东西,可以加密和验证密码。
The first thing you want to do is to remove the password
and password_confirmation
columns from your users
database. 您要做的第一件事是从
users
数据库中删除password
和password_confirmation
列。
Add a password_digest
column. 添加
password_digest
列。 And then add has_secure_password
to your model. 然后将
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
This will automatically add validations for the password, confirmation and getters and setters for password
and password_confirmation
. 这将自动添加密码验证,确认以及
password
和password_confirmation
器和设置器。
To check if the old password is correct we would do: 要检查旧密码是否正确,我们将执行以下操作:
@user = User.find(session[:id]).authenticate(params[:old_password])
# user or nil
This is an example of the Rails way of doing it: 这是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
Notice how the logic in our action is much much simpler? 请注意,我们操作中的逻辑如何简单得多? Either the user is updated and we redirect or it is invalid and we re-render the form.
用户被更新,我们将被重定向,或者它是无效的,然后我们重新呈现该表单。
Instead of creating one flash message per error we display the errors on the form: 我们不会在每个错误上显示一条Flash消息,而是在表单上显示错误:
<%= 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 %>
I would remove all code related to this password reset from the controller and put into its own model User::PasswordReset
: 我将从控制器中删除与此密码重置相关的所有代码,并将其放入自己的模型
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
That would allow to change the controller's method to something simpler like this: 这样可以将控制器的方法更改为以下更简单的方法:
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
Once you did this refactoring it should be much easier to find problems in your conditions and to extend your code. 一旦进行了这种重构,查找条件中的问题和扩展代码应该会容易得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.