Skip to content

Commit 0c0ac9d

Browse files
committed
fix problem with eager loading polymorphic associations in mongoid 9
1 parent f08e867 commit 0c0ac9d

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

lib/mongoid/includes/inclusion.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def load_documents_for(foreign_key, foreign_key_values)
5454
# Returns an Inclusion that can be eager loaded as usual.
5555
def for_class_name(class_name)
5656
Inclusion.new metadata.clone.instance_eval { |relation_metadata|
57+
@options = @options.dup
5758
@options[:class_name] = @class_name = class_name
5859
@options[:polymorphic], @options[:as], @polymorphic, @klass = nil
5960
self

spec/mongoid/includes/polymorphic_includes_spec.rb

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
require 'spec_helper'
2+
require 'open3'
3+
require 'rbconfig'
4+
require 'securerandom'
25

36
describe Mongoid::Includes::Criteria do
47

@@ -56,5 +59,127 @@
5659
}.to raise_error(Mongoid::Includes::Errors::InvalidPolymorphicIncludes)
5760
end
5861
end
62+
63+
context 'eager loading polymorphic belongs_to associations with multiple concrete types' do
64+
before(:context) do
65+
class PolyRelated
66+
include Mongoid::Document
67+
store_in collection: :poly_relateds
68+
end
69+
70+
class PolyMain
71+
include Mongoid::Document
72+
store_in collection: :poly_mains
73+
74+
belongs_to :related, polymorphic: true, optional: true
75+
end
76+
77+
class PolyTwo < PolyRelated
78+
has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related
79+
end
80+
81+
class PolyThree < PolyRelated
82+
has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related
83+
end
84+
end
85+
86+
after(:context) do
87+
%i[PolyMain PolyTwo PolyThree PolyRelated].each do |const|
88+
Object.send(:remove_const, const) if Object.const_defined?(const, false)
89+
end
90+
end
91+
92+
it 'loads the related documents for each concrete type without raising' do
93+
PolyMain.create!(related: PolyTwo.create!)
94+
PolyMain.create!(related: PolyThree.create!)
95+
96+
loaded = nil
97+
expect {
98+
loaded = PolyMain.includes(:related).entries
99+
}.not_to raise_error
100+
101+
expect(loaded.map { |doc| doc.related.class }).to match_array([PolyTwo, PolyThree])
102+
103+
expect {
104+
PolyMain.last.related.id
105+
}.not_to raise_error
106+
end
107+
end
108+
109+
context 'polymorphic eager loading in a fresh Ruby process' do
110+
let(:project_root) { File.expand_path('../../..', __dir__) }
111+
112+
it 'does not error when includes is evaluated from the CLI' do
113+
database_name = "mongoid_includes_spec_#{SecureRandom.hex(6)}"
114+
base_script = <<~RUBY
115+
require 'bundler/setup'
116+
require 'mongoid'
117+
require 'mongoid_includes'
118+
119+
Mongoid.load_configuration(
120+
clients: {
121+
default: {
122+
database: '#{database_name}',
123+
hosts: %w[localhost:27017]
124+
}
125+
}
126+
)
127+
128+
class Main
129+
include Mongoid::Document
130+
belongs_to :related, polymorphic: true, optional: true
131+
end
132+
133+
class Related
134+
include Mongoid::Document
135+
end
136+
137+
class Two < Related
138+
has_one :parent, as: :related
139+
end
140+
141+
class Three < Related
142+
has_one :parent, as: :related
143+
end
144+
RUBY
145+
146+
init_script = base_script + <<~RUBY
147+
client = Mongoid::Clients.default
148+
begin
149+
client.database.drop
150+
rescue Mongo::Error::OperationFailure
151+
end
152+
153+
Main.destroy_all
154+
Related.destroy_all
155+
156+
Main.create!(related: Two.create!)
157+
Main.create!(related: Three.create!)
158+
RUBY
159+
160+
bad_script = base_script + <<~RUBY
161+
Main.includes(:related).entries
162+
Main.last.related.id
163+
164+
Mongoid::Clients.default.database.drop
165+
RUBY
166+
167+
run_script = lambda do |script|
168+
Open3.capture2e(
169+
{ 'BUNDLE_GEMFILE' => File.join(project_root, 'Gemfile') },
170+
RbConfig.ruby,
171+
'-',
172+
chdir: project_root,
173+
stdin_data: script
174+
)
175+
end
176+
177+
init_out, init_status = run_script.call(init_script)
178+
expect(init_status).to be_success, "failed to prepare polymorphic data: #{init_out}"
179+
180+
bad_out, bad_status = run_script.call(bad_script)
181+
expect(bad_status).to be_success, "expected CLI reproduction to succeed, got #{bad_status.exitstatus}: #{bad_out}"
182+
end
183+
end
59184
end
60185
end

0 commit comments

Comments
 (0)