微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Ruby on Rails 无需密码即可更新用户属性

如何解决Ruby on Rails 无需密码即可更新用户属性

我正在尝试为用户创建一种无需密码即可更新其详细信息的方法。我在我的用户模型中使用带有 has_secure_password 的 BCrypt,以便当用户注册或更改密码时,passwordpassword_confirmation 字段在保存到 {{1} }.我还对设置密码进行了以下验证:

password_digest

因为用户可以很好地更新他们的密码(只要它至少满足 5 个字符长的验证)。但是,如果用户尝试更新他们的详细信息(我有单独的视图/表单来更新密码和更新其他非必需属性),则会出现错误提示“密码不能为空,密码太短(最少 5 个字符) )"

我已经阅读了很多以前的 stackoverflow 问题/答案,最接近的是将 validates :password,presence: true,length: { minimum: 5 } 添加到验证的末尾。这几乎可以正常工作,因为如果密码为空,BCrypt 会处理验证,因此在注册用户不能提供空密码,并且用户可以在没有密码的情况下更改他们的详细信息。但是,如果用户提供空白密码,则在更新密码时会发生一些奇怪的事情。空白密码不会保存(因为它不应该保存,因为 BCrypt 仍然需要匹配的密码和 password_confirmation,它不能为空),但是控制器的行为好像它保存并传递通知“密码更新。”。这是我在控制器中的代码

,allow_blank: true

我特别困惑为什么 def update if Current.user.update(password_params) redirect_to root_path,notice: "Password updated." else render :edit end end private def password_params params.require(:user).permit(:password,:password_confirmation) end 似乎返回 true(因此重定向到根路径并传递前面提到的通知)但密码肯定没有更新。我已通过注销并使用以前的密码重新登录进行检查,空白密码不允许我登录

解决方法

在这种情况下,最好的机会是访问 has_secure_password 的源代码。您会在那里找到以下代码:

        define_method("#{attribute}=") do |unencrypted_password|
          if unencrypted_password.nil?
            self.public_send("#{attribute}_digest=",nil)
          elsif !unencrypted_password.empty?
            instance_variable_set("@#{attribute}",unencrypted_password)
            cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
            self.public_send("#{attribute}_digest=",BCrypt::Password.create(unencrypted_password,cost: cost))
          end
        end

当您调用 has_secure_password 时,此代码将创建 setter,其中 attributepassword。它将动态创建以下方法:

        def password=(unencrypted_password)
          if unencrypted_password.nil?
            self.password_digest = nil
          elsif !unencrypted_password.empty?
            @password = unencrypted_password
            cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
            self.password_digest = BCrypt::Password.create(unencrypted_password,cost: cost))
          end
        end

当您为模型分配属性时会调用此方法:

Current.user.update(password_params)

如您所见,新定义的方法包含具有两个分支的简单条件:

  1. 如果您将 password 设置为 nil,它将删除 password_digest
  2. 如果您将 password 设置为非空字符串,它会将密码哈希存储到 password_digest 属性

仅此而已。当您的用户发送空密码时,此方法决定不执行任何操作并忽略空字符串,它不会自动分配为空密码。模型几乎完全忽略输入(如果 password_confirmation 也是空的),没有保存任何内容,没有违反验证,这意味着 Current.user.update(password_params) 返回成功并且您的控制器继续执行“密码更新”。分支。

现在,当您知道这种行为时,您能做些什么?


假设你的模型看起来像这样

class User < ApplicationRecord
  has_secure_password
  
  validates :password,presence: true,length: { minimum: 5 }
end

并且您希望您的验证在两种情况下工作

  1. 创建新用户时
  2. 当用户更新密码时

第一种情况很简单,正如您在问题中提到的,如果您使用 allow_blank: true 而不是 presence: true 验证由 has_secure_password 处理,如果密码为空,如果不是,密码将继续进行验证。

但比我们达到其他要求:

  • 用户必须能够更新其他属性,然后才可以更新密码
  • 如果密码更新,我们仍然希望验证密码

这两个要求排除了验证的 presence: true 部分,它不能存在。同样的事情也适用于 allow_blank: true。在这种情况下,您可能希望使用条件验证:

class User < ApplicationRecord
  has_secure_password
  
  validates :password,length: { minimum: 5 },if: :password_required?

private

  def password_required?
    password.present?
  end
end

此代码确保每次用户填写密码时都会执行验证。不管是 create 还是 update 操作。

现在是最后一件事。

如果用户留下空密码怎么办。 根据您的问题,我想如果用户发送空密码,您想显示验证错误。根据上面的描述,User 模型根本不知道它应该验证密码。你必须告诉你的模型,例如。像这样:

class User < ApplicationRecord
  has_secure_password
  
  validates :password,if: :password_required?

  def enforce_password_validation
    @enforce_password_validation = true
  end

private

  def password_required?
    @enforce_password_validation || password.present?
  end
end

现在您可以在控制器中像这样使用它:

  def update
    user = Current.user
    user.enforce_password_validation

    if user.update(password_params)
      redirect_to root_path,notice: "Password updated."
    else
      render :edit
    end
  end

private

  def password_params
    params.require(:user).permit(:password,:password_confirmation)
  end 

现在即使用户提交空密码,验证也会失败。

,

为了验证模型中的密码,请尝试以下操作:

验证:密码,存在:真,长度:{最小值:5},开启::创建

这一行意味着验证将只调用 :create 方法而不是 :update 。这样您就可以删除 allow_bank 并且您的更新密码将正常工作

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。