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

为何Rails调用create时忽略type字段的验证与默认值?

ActiveRecord单表继承(STI)中create/create!方法忽略type字段默认值与验证

问题现象

有多个子类继承自ActiveRecord模型Asset,调用子类的create或create!方法时出现异常:

  • type字段的数据库null: false约束被忽略,最终持久化的记录中type为nil
  • 模型层的validates_presence_of :type验证未触发,但其他验证(如validates_presence_of :name)正常工作
  • 使用子类的new方法初始化时,type字段会被正确设为子类名称(如Item),验证通过;但调用create/create!时默认值失效

代码片段

迁移文件

class CreateAssets < ActiveRecord::Migration[6.0]
  def change
    create_table :assets do |t|
      t.string :type, null: false
      t.string :name
      # ...
    end
  end
end

父类模型

class Asset < ApplicationRecord
  enum type: {
    Item: :Item,
    Accessory: :Accessory,
    Component: :Component,
    Consumable: :Consumable,
  }

  attribute :type, default: self.name

  validates_presence_of :name
  validates_presence_of :type
end

子类模型

class Item < Asset
  # ...
end

测试代码

# create方法异常情况
i = Item.create({ ... })
i.type # => nil
i.persisted? # => true
i.valid? # => false

# new方法正常情况
i = Item.new({ ... })
i.type # => "Item"
i.valid? # => true

解决方案

问题的核心是STI的type字段与自定义的enum定义冲突,同时手动设置的默认值逻辑不符合STI的工作机制:

  1. 移除type字段的enum定义:ActiveRecord的STI机制依赖type字段存储子类的类名,不能将其用作枚举字段,否则会干扰STI的自动赋值逻辑。
  2. 移除手动设置的type默认值:STI会自动为子类实例的type字段赋值为子类的类名,无需手动设置attribute :type, default: self.name(该代码在类加载时仅会赋值为"Asset",无法动态适配子类)。

修改后的Asset模型:

class Asset < ApplicationRecord
  validates_presence_of :name
  validates_presence_of :type
end

修改后,调用Item.create(...)时,STI会自动将type设为"Item",数据库约束和模型验证都会正常生效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 14:10:27