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

Rails 7.2 STI问题:取消注释关联/验证等代码触发子类未找到错误

Rails 5.2升级至7.2后STI子类识别错误问题

问题概述

将Rails应用从5.2版本升级到7.2版本后,遇到单表继承(STI)相关异常:当取消基类MyApp::BaseModel中的验证、关联、回调等代码注释时,触发错误:

Invalid single-table inheritance type: MyApp::ChildModel is not a subclass of MyApp::BaseModel (ActiveRecord::SubclassNotFound)

但保持这些代码注释时,基类与子类的查询均正常。

表结构

items表包含STI的type列,默认值为MyApp::ChildModel:

create_table "items", force: :cascade do |t|
  t.string "name"
  t.string "state"
  t.integer "category_id"
  t.string "type", default: "MyApp::ChildModel"
  t.timestamps
end

相关代码

基类代码

class MyApp::BaseModel < ApplicationRecord
  include MyApp::StiPreloader

  # Constants for STI subclasses
  PARENT_CLASS = 'MyApp::ParentModel'
  CHILD_CLASS  = 'MyApp::ChildModel'

  scope :active, -> { where(state: 'active') }
  scope :parent_items, -> { where(type: PARENT_CLASS) }
  scope :child_items, -> { where(type: CHILD_CLASS) }

  # Uncommenting any of the following lines causes the error:
  # validates :name, presence: true
  # belongs_to :category
  # before_validation :some_callback
end

子类代码

class MyApp::ChildModel < MyApp::BaseModel
  belongs_to :category

  before_validation :some_logic

  private

  def some_logic
    # Some logic here
  end
end

STI预加载器代码

module MyApp::StiPreloader
  extend ActiveSupport::Concern

  included do
    cattr_accessor :preloaded, instance_accessor: false
  end

  class_methods do
    def descendants
      return super if preloaded

      self.preloaded = true
      preload_sti
      super
    end

    def preload_sti
      return if preloaded

      types_in_db = base_class.unscoped.select(inheritance_column).distinct
                              .pluck(inheritance_column).compact
      puts "Processing STI for: #{self}"
      types_in_db.each do |type|
        begin
          type.constantize
        rescue NameError => e
          Rails.logger.warn "Skipping STI preload for #{type}: #{e.message}"
        end
      end

      self.preloaded = true
    end
  end
end

观察现象

  • 注释基类中的验证/关联/回调代码时,STI功能正常
  • 取消上述代码注释时,触发ActiveRecord::SubclassNotFound错误
  • 移除MyApp::StiPreloader后功能恢复正常
  • MyApp::BaseModel.last查询失败,但MyApp::ChildModel.last可正常执行
  • 手动执行Rails.application.eager_load!无法解决问题

已尝试的方案

  • 确认app/models/my_app/child_model.rb文件存在
  • 手动执行Rails.application.eager_load!
  • 在查询MyApp::BaseModel前调用MyApp::ChildModel.name
  • 修改STI预加载策略
  • 执行Rails.cache.clear

问题根源

核心冲突来自自定义StiPreloader与Rails 7.2的STI加载逻辑不兼容:
当基类添加验证、关联或回调时,Rails会提前触发子类的加载检测。而自定义预加载器在descendants方法中,先将preloaded标记为true再执行预加载,导致后续Rails内置的子类扫描逻辑被终止,此时MyApp::ChildModel尚未被正确识别为BaseModel的子类,从而抛出错误。

解决方案

方案1:移除自定义STI预加载器

Rails 7+默认使用Zeitwerk自动加载器,已经内置了完善的STI子类自动识别机制,无需手动实现预加载逻辑:

  1. 删除MyApp::StiPreloader模块文件
  2. 移除基类中的include MyApp::StiPreloader代码

方案2:修复自定义预加载器逻辑

若必须保留预加载器,调整descendants方法逻辑,确保子类先被加载再返回结果:

module MyApp::StiPreloader
  extend ActiveSupport::Concern

  included do
    cattr_accessor :preloaded, instance_accessor: false
  end

  class_methods do
    def descendants
      preload_sti unless preloaded
      super
    end

    def preload_sti
      return if preloaded

      types_in_db = base_class.unscoped.select(inheritance_column).distinct
                              .pluck(inheritance_column).compact
      types_in_db.each do |type|
        begin
          klass = type.constantize
          # 验证子类继承关系,避免无效类触发异常
          Rails.logger.warn "#{type} is not a subclass of #{self}" unless klass < self
        rescue NameError => e
          Rails.logger.warn "Skipping STI preload for #{type}: #{e.message}"
        end
      end

      self.preloaded = true
    end
  end
end

方案3:强制提前加载子类

在config/initializers/sti_preload.rb中添加初始化代码,确保子类在应用启动时被加载:

Rails.application.config.after_initialize do
  MyApp::ChildModel.name
  MyApp::ParentModel.name
end

验证步骤

  1. 应用上述任一解决方案后,取消基类中验证/关联/回调的注释
  2. 执行MyApp::BaseModel.last测试是否正常返回结果
  3. 检查开发环境下的自动加载逻辑是否正常工作

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 21:08:16