最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

zeitwerk - Rails 7.2 STI: Invalid single-table inheritance type error when uncommenting relations, validations or state machine

programmeradmin2浏览0评论

I recently upgraded my Rails application from Rails 5.2 to Rails 7.2 and I am facing an issue related to Single Table Inheritance (STI).

Issue: Whenever I uncomment certain parts of my base model (e.g., validations, associations, callbacks), I get the following error:

(main):16:in `': Invalid single-table inheritance type: MyApp::ChildModel is not a subclass of MyApp::BaseModel (ActiveRecord::SubclassNotFound)

However, if I keep those lines commented, everything works fine, and I can successfully query both the base model and its subclasses.

My items table has an STI column type:

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

Base Model:

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

Child Model:

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

  before_validation :some_logic

  private

  def some_logic
    # Some logic here
  end
end

My STI Preloader:

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)pact
      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

Observations: If I comment out certain lines in MyApp::BaseModel, STI works fine.

If I uncomment validations, associations, callbacks, or even state_machine I get the ActiveRecord::SubclassNotFound error.

MyApp::StiPreloader is being used to preload STI subclasses.(Everything works with commenting this aswell )

MyApp::BaseModel.last fails, but MyApp::ChildModel.last works fine.

Running Rails.application.eager_load! does not resolve the issue.

Things I Have Tried:

Ensuring app/models/my_app/child_model.rb exists.

Running Rails.application.eager_load! manually.

Calling MyApp::ChildModel.name before querying MyApp::BaseModel.

Changing the STI preload strategy.

Running Rails.cache.clear.

I recently upgraded my Rails application from Rails 5.2 to Rails 7.2 and I am facing an issue related to Single Table Inheritance (STI).

Issue: Whenever I uncomment certain parts of my base model (e.g., validations, associations, callbacks), I get the following error:

(main):16:in `': Invalid single-table inheritance type: MyApp::ChildModel is not a subclass of MyApp::BaseModel (ActiveRecord::SubclassNotFound)

However, if I keep those lines commented, everything works fine, and I can successfully query both the base model and its subclasses.

My items table has an STI column type:

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

Base Model:

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

Child Model:

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

  before_validation :some_logic

  private

  def some_logic
    # Some logic here
  end
end

My STI Preloader:

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)pact
      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

Observations: If I comment out certain lines in MyApp::BaseModel, STI works fine.

If I uncomment validations, associations, callbacks, or even state_machine I get the ActiveRecord::SubclassNotFound error.

MyApp::StiPreloader is being used to preload STI subclasses.(Everything works with commenting this aswell )

MyApp::BaseModel.last fails, but MyApp::ChildModel.last works fine.

Running Rails.application.eager_load! does not resolve the issue.

Things I Have Tried:

Ensuring app/models/my_app/child_model.rb exists.

Running Rails.application.eager_load! manually.

Calling MyApp::ChildModel.name before querying MyApp::BaseModel.

Changing the STI preload strategy.

Running Rails.cache.clear.

Share Improve this question asked Mar 16 at 20:17 Burhan GardeziBurhan Gardezi 3311 gold badge2 silver badges11 bronze badges 2
  • Add some reproducible example. As I see now, it will not work in any case. Because items table name doesn't match your models name – mechnicov Commented Mar 16 at 21:09
  • 3 Do not use :: for namespace defintion unless you want to have very weird and suprising constant lookups and autoloader issues. In combination with STI it's basically asking for trouble. github/rubocop/… – max Commented Mar 16 at 22:15
Add a comment  | 

1 Answer 1

Reset to default 0

As mentioned by Max on the comments, avoid using the MyApp:: in the default column value, but if the issue persists, try emptying your database and executing the same changes with no data in the database. If the application works, you have an issue in your database records, some of them might have broken relationships for some reason

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论