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 |1 Answer
Reset to default 0As 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
items
table name doesn't match your models name – mechnicov Commented Mar 16 at 21:09::
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