Skip to content

Commit

Permalink
MONGOID-5839 Fix eager-loading from STI subclasses
Browse files Browse the repository at this point in the history
If the root of a query is an STI subclass (e.g. `Subclass.all`)
AND the query tries to eager load (`includes`) another association,
the eager load was failing because it was looking for inverse
associations using the STI subclass name, instead of the class at
the root of the hierarchy.
  • Loading branch information
jamis committed Jan 15, 2025
1 parent ec18c8b commit df399bc
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/mongoid/association/eager_loadable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def preload(associations, docs)
docs_map = {}
queue = [ klass.to_s ]

# account for single-collection inheritance
queue.push(klass.root_class.to_s) if klass != klass.root_class

while klass = queue.shift
if as = assoc_map.delete(klass)
as.each do |assoc|
Expand Down
12 changes: 12 additions & 0 deletions lib/mongoid/traversable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ def hereditary?
!!(superclass < Mongoid::Document)
end

# Returns the root class of the STI tree that the current
# class participates in. If the class is not an STI subclass, this
# returns the class itself.
#
# @return [ Mongoid::Document ] the root of the STI tree
def root_class
root = self
root = root.superclass while root.hereditary?

root
end

# When inheriting, we want to copy the fields from the parent class and
# set the on the child to start, mimicking the behavior of the old
# class_inheritable_accessor that was deprecated in Rails edge.
Expand Down
26 changes: 24 additions & 2 deletions spec/mongoid/association/eager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,36 @@
Mongoid::Contextual::Mongo.new(criteria)
end

let(:association_host) { Account }

let(:inclusions) do
includes.map do |key|
Account.reflect_on_association(key)
association_host.reflect_on_association(key)
end
end

let(:doc) { criteria.first }

context 'when root is an STI subclass' do
# Driver has_one Vehicle
# Vehicle belongs_to Driver
# Truck is a Vehicle

before do
Driver.create!(vehicle: Truck.new)
end

let(:criteria) { Truck.all }
let(:includes) { %i[ driver ] }
let(:association_host) { Truck }

it 'preloads the driver' do
expect(doc.ivar(:driver)).to be false
context.preload(inclusions, [ doc ])
expect(doc.ivar(:driver)).to be == Driver.first
end
end

context "when belongs_to" do

let!(:account) do
Expand All @@ -43,7 +65,7 @@
it "preloads the parent" do
expect(doc.ivar(:person)).to be false
context.preload(inclusions, [doc])
expect(doc.ivar(:person)).to eq(doc.person)
expect(doc.ivar(:person)).to be == person
end
end

Expand Down

0 comments on commit df399bc

Please sign in to comment.