简体   繁体   English

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

[英]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> 

However 然而

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数据库中删除passwordpassword_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 . 这将自动添加密码验证,确认以及passwordpassword_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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM