Rails控制器参数条件-如何干净地做到这一点



我需要检查控制器方法中的一组条件。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数据库中删除passwordpassword_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

这将自动添加密码验证、确认以及passwordpassword_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

一旦进行了重构,就应该更容易发现条件中的问题并扩展代码。

相关内容

最新更新