为什么我在用户更新期间没有更新密码时得到 "Password can't be blank"(Rails 4 和 'has_secure_password')?

Why am I getting "Password can't be blank" during user update when not updating the password (Rails 4 with 'has_secure_password')?

在我的 Rails 4.0.9 应用程序中,使用 BCrypt 3.1.7 和 Clearance 1.4.2 进行身份验证,更新用户信息时返回错误。尽管没有输入新密码或密码确认,我还是收到错误 "Password can't be blank."

我对 SO 和谷歌搜索的评价高低不一,none 我找到的解决方案对我有用。大多数是针对 Rails 的早期版本,现在可能已经过时了,而其他的并不真正适合我的确切情况(可能是因为我只是在做一些愚蠢的事情)。

我可以从编辑表单中删除密码和 password_confirmation 字段,但我 希望用户能够更新他们的密码,同时也更新他们的名字或电子邮件地址,如果他们愿意的话。

'has_secure_password' 应该只在创建时验证,但似乎也在更新时验证。为了解决这个问题,我禁用了我的用户模型对密码和 password_confirmation 的验证,并简单地使用 'has_secure_password' 来验证这些参数。你能看出我为什么会收到验证错误吗?许可 gem 是否覆盖 has_secure_passwords 的默认行为,或以任何方式影响验证?

用户模型

class User < ActiveRecord::Base
include Clearance::User
belongs_to :studio
has_one :public_user

before_save { self.email = email.downcase }
before_create :create_remember_token
after_create :make_public_user

validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
validates :email, presence: true,
                format:     { with: VALID_EMAIL_REGEX },
                uniqueness: { case_sensitive: false }
has_secure_password
#   validates :password, length: { minimum: 6, on: :create }
#   validates :password_confirmation, presence: { on: :create }
...

用户控制器

...
def edit
end

def update
  if @user.update_attributes(user_params)
    flash[:success] = "Profile updated"
    redirect_to @user
  else
    render 'edit'
  end
end

private


def user_params
  params.require(:user).permit(:name, :email, :password, :password_confirmation, :password_digest)
end
...

编辑表格(实际上是“_fields.html.erb”部分)

<%= render 'shared/error_messages', object: f.object %>

<div class="control-group">
        <%= f.label :name, class: "control-label" %>
<div class="controls">
        <%= f.text_field :name %>
    </div>
</div>

<div class="control-group">
    <%= f.label :email, class: "control-label" %>
<div class="controls">
        <%= f.text_field :email %>
    </div>
</div>

<div class="control-group">
    <%= f.label :password, class: "control-label" %>
<div class="controls">
        <%= f.password_field :password, placeholder: 'Enter new password if changing' %>
    </div>
</div>

<div class="control-group">
    <%= f.label :password_confirmation, "Confirmation", class: "control-label" %>
    <div class="controls">
        <%= f.password_field :password_confirmation, placeholder: 'Confirm your new password if changing' %>
    </div>
</div>

传递给控制器​​的参数

Parameters: {"utf8"=>"✓", "authenticity_token"=>"[FILTERED]", "user"=>{"name"=>"Ransom Kshlerin III", "email"=>"example-1@samplestudio.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Save changes", "id"=>"4"}

编辑:

当我禁用 'has_secure_password' 时,我仍然收到验证错误,这让我相信 Clearance 的验证正在返回错误。我认为我需要覆盖 Clearance 的密码验证,但仅在更新用户时才需要。我该怎么办?

编辑 2:

我将采用完全不同的方法。我将提供一个 link 用于重置密码,而不是直接在此表单上允许密码重置,这将向用户发送一封包含 link 的电子邮件以重置他们的密码。

在传递给控制器​​的参数中存在 "password"=>"", "password_confirmation"=>""(不用担心 [FILTERED],这仅意味着 Rails 不会将参数打印到日志中) .

当调用 params.require(:user).permit(:password, :password_confirmation) 时包含 { password: "", password_confirmation: "", # plus the other keys }

将此哈希值传递给 @user.update_attributes({password: '', password_confirmation: ''}) 时,密码的存在验证将启动并防止设置空密码。

不尝试设置空密码的一个选项可能是从 user_params 哈希中删除键,除非它们的值被认为存在。

def user_params
  user_params = params.require(:user).permit(:name, :email, :password, :password_confirmation) # don't permit `password_digest` here, that database column should not be allowed to be set by the user!

  # Remove the password and password confirmation keys for empty values
  user_params.delete(:password) unless user_params[:password].present?
  user_params.delete(:password_confirmation) unless user_params[:password_confirmation].present?

  user_params
end

has_secure_password 和 Clearance gem 各自验证密码和密码确认,我还没有足够的技能来覆盖 Clearance 的验证(在我的头脑中)。所以我采取了不同的方法。密码和密码确认将不会包含在此编辑表单中。

我将提供 link 用于重置密码,而不是直接在此表单上允许重置密码,这将向用户发送一封包含 link 的电子邮件以重置他们的密码。我想这里的额外好处是,如果用户设法以另一个用户的身份登录,他们将无法重置该用户的密码,除非他们也侵入了其他用户的电子邮件帐户。

许可提供了一个明确的 (^_^) 路径来规避密码要求:

# Always false. Override this method in your user model to allow for other
# forms of user authentication (username, Facebook, etc).
#
# @return [false]
def password_optional?
  false
end

例如,在您的用户模型中,您可以这样做:

# app/models/user.rb

class User < ApplicationRecord
  include Clearance::User

  def password_optional?
    true
  end
end

Source