1
votes

STI is not working as I think it should under certain conditions.

The models (simplified):

class Order < ApplicationRecord
  has_many :items, class_name: 'OrderItem', inverse_of: :order
end

class OrderItem < ApplicationRecord
  belongs_to :order, inverse_of: :items
  has_one :spec, inverse_of: :order_item
end

class Spec < ApplicationRecord
  belongs_to :order_item, inverse_of: :spec
end

class FooSpec < Spec
end

class BarSpec < Spec
end

Schema (simplified):

create_table "specs", force: :cascade do |t|
  t.string "type", null: false
  t.bigint "order_item_id", null: false
  ...
end

I'm using ActiveRecord::Associations::Preloader to avoid n+1 problems in my GraphQL server. I've started getting some STI errors.

From a console, this works fine and returns a FooSpec or BarSpec:

Order.includes(items: :spec).first.items.first.spec

Same with this:

Order.includes(:items).first.items.includes(:spec).first.spec

And this too:

ActiveRecord::Associations::Preloader.new
  .preload(Order.all, :items).first.owners.first.items.first.spec

However, this:

ActiveRecord::Associations::Preloader.new.preload(Order.all, items: :spec)

And this:

ActiveRecord::Associations::Preloader.new
  .preload(Order.all.map{|o| o.items}.flatten, :spec)

Raise an error:

ActiveRecord::SubclassNotFound: The single-table inheritance mechanism failed to locate the subclass: 'FooSpec'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite Spec.inheritance_column to use another column for that information.

Sometimes I'm getting a slightly different error from the app but I cannot reproduce it in the console:

ActiveRecord::SubclassNotFound (Invalid single-table inheritance type: FooSpec is not a subclass of Spec)

Hmm...as you can see, FooSpec is most definitely a subclass of Spec. These errors usually mean you used type as an attribute and did not tell ActiveRecord it's not an STI discriminator. That's not the case here. Ideas?

1

1 Answers

0
votes

I found the culprit. I had this:

class FooSpec < Spec
  validates :my_field, inclusion: {in: MyClass::CHOICES}
end

When I changed it to this, the problem disappeared:

class FooSpec < Spec
  validates :my_field, inclusion: {in: -> (_instance) {MyClass::CHOICES}}
end

I do not understand why. MyClass::CHOICES is a simple constant holding an array. The class is a classy_enum.

class MyClass < MyClassyEnum
  CHOICES = %w[
    choiceOne
    choiceTwo
  ].freeze
  ...
end