You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails控制台可生成password_digest,控制器逻辑为何失效?

手动实现Rails身份认证时,表单创建用户的password_digest始终为nil

我手动实现Rails身份认证(不使用Devise),集成ActiveModel::SecurePassword时遇到异常:

  • Rails控制台创建用户时,password_digest能正常生成,验证也生效
  • 通过注册表单调用控制器逻辑创建用户时,password_digest始终为nil

已尝试的操作:

  1. 在User模型中引入ActiveModel::SecurePassword
  2. 启用bcrypt gem并执行bundle install
  3. 控制台操作正常(示例代码):
3.2.1 :137 > new_user = User.new(first_name: "test", last_name: "tester", email: "foo@bar.com", password: "123123123")
 => #<User:0x0000000107c5bf40 id: nil, first_name: "test", last_name: "tester", email: "foo@bar.com", password: nil, created_at: nil, updated_at: nil>
3.2.1 :138 > new_user.save
  TRANSACTION (0.7ms)  BEGIN
  User Exists? (0.8ms)  SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2  [["email", "foo@bar.com"], ["LIMIT", 1]]
  User Create (0.5ms)  INSERT INTO "users" ("first_name", "last_name", "email", "password", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["first_name", "test"], ["last_name", "tester"], ["email", "foo@bar.com"], ["password", "[FILTERED]"], ["created_at", "2023-03-07 03:36:07.501992"], ["updated_at", "2023-03-07 03:36:07.501992"]]
  TRANSACTION (1.3ms)  COMMIT
 => true
3.2.1 :139 > new_user.password_digest
 => "$2a$12$aIcAuAdSnJhrLw0bWHTh1OgTTmUioB2ra3h12cFNs/IwnJdyRYJEW"
# 注:密码已通过Bcrypt成功加密

但表单创建的用户password_digest为nil:

3.2.1 :140 > latest_user = User.last
  User Load (1.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1  [["LIMIT", 1]]
 => #<User:0x0000000107d75ca0 id: 27, first_name: "Allday", last_name: "Everyday", email: "allday@everyday.com", password: nil, created_at: Tue, 07 Mar 2023 03:39:13.986217000 UTC +00:00, updated_at: Tue, 07 Mar 2023 03:39:13.986217000 UTC +00:00>
3.2.1 :141 > latest_user.password_digest
 => nil
# 注:digest为nil!

相关代码

user.rb

# == Schema Information
#
# Table name: users
#
#  id              :bigint           not null, primary key
#  email           :string
#  first_name      :string
#  last_name       :string
#  password        :string
#  password_digest :string
#  created_at      :datetime         not null
#  updated_at      :datetime         not null
#
class User < ApplicationRecord
  include ActiveModel::SecurePassword
  has_secure_password
  has_secure_password :recovery_password, validations: false
  
  before_save :downcase_email

  validates :email, format: {with: URI::MailTo::EMAIL_REGEXP}, uniqueness: true, presence: true
  validates :first_name, length: {minimum: 2, maximum: 100}, presence: true
  validates :last_name, length: {minimum: 2, maximum: 100}, presence: true
  validates :password, length: {minimum: 8, maximum: 25}, presence: true

  attr_accessor :password_digest, :recovery_password_digest

  private
  def downcase_email
    self.email = email.downcase
  end
end

user_controller.rb

class UserController < ApplicationController
  # post '/user/register'
  def create
    user = User.new(user_params)
    begin
      user.save!
      session[:user_id] = user.id
      return render json: {success: true, status: 200}
    rescue => exception
      return render json: {success: false, status: 422, error: exception.message}
    end
  end

  private
  def user_params
    params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation)
  end
end

问题原因及解决方案

核心问题

你在User模型中添加的attr_accessor :password_digest, :recovery_password_digest完全覆盖了Rails为数据库字段自动生成的getter/setter方法。

has_secure_password的工作逻辑是监听password属性的赋值,自动生成加密后的password_digest并写入数据库列。但手动定义的attr_accessor把password_digest变成了内存中的虚拟属性,不再关联数据库字段,导致生成的digest无法持久化。

控制台操作看似正常,是因为save后直接调用实例的password_digest取的是内存中的临时值,如果你重新查询该用户,会发现数据库里的password_digest同样是nil。

修复步骤

直接删除User模型中的这一行:

attr_accessor :password_digest, :recovery_password_digest

额外优化建议

  1. 数据库表中不需要password字段,has_secure_password只需要password_digest列存储加密后的密码,password只是用于接收明文输入的虚拟属性,建议移除数据库中的password字段。
  2. include ActiveModel::SecurePassword可以省略,因为ApplicationRecord继承自ActiveRecord::Base,has_secure_password会自动引入所需模块。

内容的提问来源于stack exchange,提问作者twk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 07:13:22