Trying to reflect polymorphism behaviour on STI considering preloading

With polymorphism

class Image < ActiveRecord::Base
  belongs_to :illustrable, polymorphic: true
end
class Post < ActiveRecord::Base
  has_many :images, as: :illustrable
  has_many :comments
end
class Comment < ActiveRecord::Base
  has_many :images, as: :illustrable
end

I can preload dependent association thanks to Support preloads on instance dependent associations by jhawthorn · Pull Request #42553 · rails/rails · GitHub

 Image.includes(illustrable: :comments).to_a

With STI

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class TextComment < Comment
end

class VideoComment < Comment
  belongs_to :video
end

class Video < ActiveRecord::Base
  has_many :comments
end

I can’t

Post.preload(comments: :video).to_a

ActiveRecord::AssociationNotFoundError: Association named 'video' was not found on Comment; perhaps you misspelled it?

I tried to patch, just for a test lib/active_record/associations/preloader/branch.rb#grouped_records

 def grouped_records
          h = {}
          polymorphic_parent = !root? && parent.polymorphic?
          source_records.each do |record|
            reflection = record.class._reflect_on_association(association)
            next if !reflection
            next if !record.association(association).klass
            (h[reflection] ||= []) << record
          end
          source_records.first.association(association) if !source_records.empty? && h.empty? && !polymorphic_parent
          h
end

But It doesn’t work if source_records are not records having the relations:

 test "preloading of instance dependent (sti) associations is supported" do
    pirate_1 = Pirate.create!(catchphrase: "Yarr")
    pirate_2 = Pirate.create!(catchphrase: "Ahoy")
    parrot = Parrot.create!(name: "Polly 1", pirates: [pirate_1])
    parrot = Parrot.create!(name: "Polly 2", pirates: [pirate_2])
    dparrot = DeadParrot.create!(killer: pirate_1, name: "D Polly 2", pirates: [pirate_2])
    pirate = Pirate.preload(parrots: :killer).find(pirate_2.id)
    assert_queries_count(0) {
      assert_equal pirate_1, pirate.parrots.second.killer
    }
    assert_equal [], Pirate.none.preload(parrots: :killer).to_a
    assert_equal pirate_1, Pirate.where(id: pirate_1.id).preload(parrots: :killer).first
  end

The last test fail

Do you see a better way to do it ? Ideally a test like parent.polymorphic? which say that source_records are STI records

Thanks

It’s the same topic Should Associations::Preloader allow subclasses not to define the association? but trying to tackle it.

I started a PR on my side to implement this behaviour : Don't raise on association not found if any sti class in the hierarch… by simkim · Pull Request #1 · simkim/rails · GitHub