为何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的工作机制:
- 移除
type字段的enum定义:ActiveRecord的STI机制依赖type字段存储子类的类名,不能将其用作枚举字段,否则会干扰STI的自动赋值逻辑。 - 移除手动设置的
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
相关产品推荐
相关产品推荐

