Ruby 于 Rails 更新无密码的用户属性
Ruby on Rails update user attributes without password
我正在尝试创建一种方法,让用户无需密码即可更新其详细信息。我在我的用户模型中将 BCrypt 与 has_secure_password
一起使用,这样当用户注册或更改密码时,password
和 password_confirmation
字段会在保存到 [=16= 之前进行匹配检查].我还有以下设置密码的验证:
validates :password, presence: true, length: { minimum: 5 }
因为用户可以更新他们的密码(只要密码至少有 5 个字符来满足验证)。但是如果用户试图更新他们的详细信息(我有单独的 views/forms 用于更新密码和更新其他非必需属性)然后它会给出一个错误提示“密码不能为空,密码太短(最小值是5 个字符)"
我已经通读了很多以前的 Whosebug questions/answers,最接近的是将 , allow_blank: true
添加到验证的末尾。这几乎符合我的要求,因为如果密码为空,BCrypt 会处理验证,因此在注册时用户不能提供空白密码,并且用户可以在没有密码的情况下更改他们的详细信息。但是,在更新密码时,如果用户提供空白密码,则会发生一些奇怪的事情。空白密码不会保存(因为它不应该因为 BCrypt 仍然需要一个匹配的密码和 password_confirmation,它不能为空),但是控制器就像它一样并传递通知“密码更新。”。这是我在控制器中的代码:
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
我特别困惑为什么 Current.user.update(password_params)
似乎返回 true(因此重定向到根路径并传递前面提到的通知)但密码肯定没有更新。我已经通过注销并使用之前的密码重新登录进行了检查,但空白密码不允许我登录。
要在您的模型中验证密码,请试试这个:
验证 :password, presence: true, 长度: { minimum: 5 }, on: :create
这一行意味着验证将只调用 :create 方法而不是 :update 了。这样您就可以删除 allow_bank 并且您的更新密码将正常工作
在这种情况下,最好的机会是查看 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,其中 attribute
是 password
。它将动态创建以下方法:
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)
如您所见,新定义的方法包含带有两个分支的简单条件:
- 如果您将
password
设置为 nil
,它将删除 password_digest
- 如果您将
password
设置为非空字符串,它会将密码哈希存储到 password_digest
属性
就是这样。当您的用户发送空密码时,此方法决定什么都不做并忽略空字符串,它不会自动分配为空密码。模型几乎完全忽略输入(如果 password_confirmation
也是空的),没有任何内容被保存,没有违反验证,这意味着 Current.user.update(password_params)
returns 成功并且您的控制器继续“密码更新”。分支.
现在,当您知道这种行为时,您能做些什么呢?
假设您的模特看起来像这样
class User < ApplicationRecord
has_secure_password
validates :password, presence: true, length: { minimum: 5 }
end
并且您希望验证在两种情况下有效
- 创建新用户时
- 当用户更新密码时
第一种情况很简单,正如您在问题中提到的,如果您使用 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, length: { minimum: 5 }, 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
现在即使用户提交空密码,验证也会失败。
我正在尝试创建一种方法,让用户无需密码即可更新其详细信息。我在我的用户模型中将 BCrypt 与 has_secure_password
一起使用,这样当用户注册或更改密码时,password
和 password_confirmation
字段会在保存到 [=16= 之前进行匹配检查].我还有以下设置密码的验证:
validates :password, presence: true, length: { minimum: 5 }
因为用户可以更新他们的密码(只要密码至少有 5 个字符来满足验证)。但是如果用户试图更新他们的详细信息(我有单独的 views/forms 用于更新密码和更新其他非必需属性)然后它会给出一个错误提示“密码不能为空,密码太短(最小值是5 个字符)"
我已经通读了很多以前的 Whosebug questions/answers,最接近的是将 , allow_blank: true
添加到验证的末尾。这几乎符合我的要求,因为如果密码为空,BCrypt 会处理验证,因此在注册时用户不能提供空白密码,并且用户可以在没有密码的情况下更改他们的详细信息。但是,在更新密码时,如果用户提供空白密码,则会发生一些奇怪的事情。空白密码不会保存(因为它不应该因为 BCrypt 仍然需要一个匹配的密码和 password_confirmation,它不能为空),但是控制器就像它一样并传递通知“密码更新。”。这是我在控制器中的代码:
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
我特别困惑为什么 Current.user.update(password_params)
似乎返回 true(因此重定向到根路径并传递前面提到的通知)但密码肯定没有更新。我已经通过注销并使用之前的密码重新登录进行了检查,但空白密码不允许我登录。
要在您的模型中验证密码,请试试这个:
验证 :password, presence: true, 长度: { minimum: 5 }, on: :create
这一行意味着验证将只调用 :create 方法而不是 :update 了。这样您就可以删除 allow_bank 并且您的更新密码将正常工作
在这种情况下,最好的机会是查看 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,其中 attribute
是 password
。它将动态创建以下方法:
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)
如您所见,新定义的方法包含带有两个分支的简单条件:
- 如果您将
password
设置为nil
,它将删除password_digest
- 如果您将
password
设置为非空字符串,它会将密码哈希存储到password_digest
属性
就是这样。当您的用户发送空密码时,此方法决定什么都不做并忽略空字符串,它不会自动分配为空密码。模型几乎完全忽略输入(如果 password_confirmation
也是空的),没有任何内容被保存,没有违反验证,这意味着 Current.user.update(password_params)
returns 成功并且您的控制器继续“密码更新”。分支.
现在,当您知道这种行为时,您能做些什么呢?
假设您的模特看起来像这样
class User < ApplicationRecord
has_secure_password
validates :password, presence: true, length: { minimum: 5 }
end
并且您希望验证在两种情况下有效
- 创建新用户时
- 当用户更新密码时
第一种情况很简单,正如您在问题中提到的,如果您使用 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, length: { minimum: 5 }, 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
现在即使用户提交空密码,验证也会失败。