Skip to content

Commit

Permalink
MONGOID-5839 Fix eager-loading from STI subclasses (backport to 8.0)
Browse files Browse the repository at this point in the history
(backport to 8.0)

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 22, 2025
1 parent ed8bc62 commit ad0ee73
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 @@ -31,6 +31,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 @@ -300,6 +300,18 @@ def hereditary?
!!(Mongoid::Document > superclass)
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 @@ -14,14 +14,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 @@ -42,7 +64,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 ad0ee73

Please sign in to comment.