From 0c0ac9dfc4bb58c6e6a143447fe191044ac280a7 Mon Sep 17 00:00:00 2001 From: Daniel Arnold Date: Sat, 4 Oct 2025 17:02:34 -0400 Subject: [PATCH 1/4] fix problem with eager loading polymorphic associations in mongoid 9 --- lib/mongoid/includes/inclusion.rb | 1 + .../includes/polymorphic_includes_spec.rb | 125 ++++++++++++++++++ 2 files changed, 126 insertions(+) diff --git a/lib/mongoid/includes/inclusion.rb b/lib/mongoid/includes/inclusion.rb index 871b2ac..d61bff2 100644 --- a/lib/mongoid/includes/inclusion.rb +++ b/lib/mongoid/includes/inclusion.rb @@ -54,6 +54,7 @@ def load_documents_for(foreign_key, foreign_key_values) # Returns an Inclusion that can be eager loaded as usual. def for_class_name(class_name) Inclusion.new metadata.clone.instance_eval { |relation_metadata| + @options = @options.dup @options[:class_name] = @class_name = class_name @options[:polymorphic], @options[:as], @polymorphic, @klass = nil self diff --git a/spec/mongoid/includes/polymorphic_includes_spec.rb b/spec/mongoid/includes/polymorphic_includes_spec.rb index 3c933f7..1412ccb 100644 --- a/spec/mongoid/includes/polymorphic_includes_spec.rb +++ b/spec/mongoid/includes/polymorphic_includes_spec.rb @@ -1,4 +1,7 @@ require 'spec_helper' +require 'open3' +require 'rbconfig' +require 'securerandom' describe Mongoid::Includes::Criteria do @@ -56,5 +59,127 @@ }.to raise_error(Mongoid::Includes::Errors::InvalidPolymorphicIncludes) end end + + context 'eager loading polymorphic belongs_to associations with multiple concrete types' do + before(:context) do + class PolyRelated + include Mongoid::Document + store_in collection: :poly_relateds + end + + class PolyMain + include Mongoid::Document + store_in collection: :poly_mains + + belongs_to :related, polymorphic: true, optional: true + end + + class PolyTwo < PolyRelated + has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related + end + + class PolyThree < PolyRelated + has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related + end + end + + after(:context) do + %i[PolyMain PolyTwo PolyThree PolyRelated].each do |const| + Object.send(:remove_const, const) if Object.const_defined?(const, false) + end + end + + it 'loads the related documents for each concrete type without raising' do + PolyMain.create!(related: PolyTwo.create!) + PolyMain.create!(related: PolyThree.create!) + + loaded = nil + expect { + loaded = PolyMain.includes(:related).entries + }.not_to raise_error + + expect(loaded.map { |doc| doc.related.class }).to match_array([PolyTwo, PolyThree]) + + expect { + PolyMain.last.related.id + }.not_to raise_error + end + end + + context 'polymorphic eager loading in a fresh Ruby process' do + let(:project_root) { File.expand_path('../../..', __dir__) } + + it 'does not error when includes is evaluated from the CLI' do + database_name = "mongoid_includes_spec_#{SecureRandom.hex(6)}" + base_script = <<~RUBY + require 'bundler/setup' + require 'mongoid' + require 'mongoid_includes' + + Mongoid.load_configuration( + clients: { + default: { + database: '#{database_name}', + hosts: %w[localhost:27017] + } + } + ) + + class Main + include Mongoid::Document + belongs_to :related, polymorphic: true, optional: true + end + + class Related + include Mongoid::Document + end + + class Two < Related + has_one :parent, as: :related + end + + class Three < Related + has_one :parent, as: :related + end + RUBY + + init_script = base_script + <<~RUBY + client = Mongoid::Clients.default + begin + client.database.drop + rescue Mongo::Error::OperationFailure + end + + Main.destroy_all + Related.destroy_all + + Main.create!(related: Two.create!) + Main.create!(related: Three.create!) + RUBY + + bad_script = base_script + <<~RUBY + Main.includes(:related).entries + Main.last.related.id + + Mongoid::Clients.default.database.drop + RUBY + + run_script = lambda do |script| + Open3.capture2e( + { 'BUNDLE_GEMFILE' => File.join(project_root, 'Gemfile') }, + RbConfig.ruby, + '-', + chdir: project_root, + stdin_data: script + ) + end + + init_out, init_status = run_script.call(init_script) + expect(init_status).to be_success, "failed to prepare polymorphic data: #{init_out}" + + bad_out, bad_status = run_script.call(bad_script) + expect(bad_status).to be_success, "expected CLI reproduction to succeed, got #{bad_status.exitstatus}: #{bad_out}" + end + end end end From d7efb94f0f2b3e85a705d20374b723b2aa8ea8e5 Mon Sep 17 00:00:00 2001 From: Daniel Arnold Date: Sat, 4 Oct 2025 18:15:53 -0400 Subject: [PATCH 2/4] update test to use ENV['BUNDLE_GEMFILE'] when present to fix older mongoid tests --- spec/mongoid/includes/polymorphic_includes_spec.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/mongoid/includes/polymorphic_includes_spec.rb b/spec/mongoid/includes/polymorphic_includes_spec.rb index 1412ccb..b939be7 100644 --- a/spec/mongoid/includes/polymorphic_includes_spec.rb +++ b/spec/mongoid/includes/polymorphic_includes_spec.rb @@ -164,9 +164,14 @@ class Three < Related Mongoid::Clients.default.database.drop RUBY + bundle_gemfile = ENV.fetch( + 'BUNDLE_GEMFILE', + File.join(project_root, 'Gemfile') + ) + run_script = lambda do |script| Open3.capture2e( - { 'BUNDLE_GEMFILE' => File.join(project_root, 'Gemfile') }, + { 'BUNDLE_GEMFILE' => bundle_gemfile }, RbConfig.ruby, '-', chdir: project_root, From 777c36f667ff83507b8a3001ce58867630c3981e Mon Sep 17 00:00:00 2001 From: Daniel Arnold Date: Sat, 4 Oct 2025 18:20:21 -0400 Subject: [PATCH 3/4] move require for open3 to where it's needed, and remove unnecessary requires for rbconfig and securerandom --- spec/mongoid/includes/polymorphic_includes_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/mongoid/includes/polymorphic_includes_spec.rb b/spec/mongoid/includes/polymorphic_includes_spec.rb index b939be7..8aa1822 100644 --- a/spec/mongoid/includes/polymorphic_includes_spec.rb +++ b/spec/mongoid/includes/polymorphic_includes_spec.rb @@ -1,7 +1,4 @@ require 'spec_helper' -require 'open3' -require 'rbconfig' -require 'securerandom' describe Mongoid::Includes::Criteria do @@ -110,6 +107,8 @@ class PolyThree < PolyRelated let(:project_root) { File.expand_path('../../..', __dir__) } it 'does not error when includes is evaluated from the CLI' do + require 'open3' + database_name = "mongoid_includes_spec_#{SecureRandom.hex(6)}" base_script = <<~RUBY require 'bundler/setup' From ab6d19ba896a06bf5a918da8c98acbac482a2fe6 Mon Sep 17 00:00:00 2001 From: Jarrett Lusso Date: Sun, 5 Oct 2025 09:33:00 -0400 Subject: [PATCH 4/4] Shortened and simplified tests --- .../includes/polymorphic_includes_spec.rb | 45 ++++++------------- 1 file changed, 14 insertions(+), 31 deletions(-) diff --git a/spec/mongoid/includes/polymorphic_includes_spec.rb b/spec/mongoid/includes/polymorphic_includes_spec.rb index 8aa1822..8dba4e0 100644 --- a/spec/mongoid/includes/polymorphic_includes_spec.rb +++ b/spec/mongoid/includes/polymorphic_includes_spec.rb @@ -59,47 +59,34 @@ context 'eager loading polymorphic belongs_to associations with multiple concrete types' do before(:context) do - class PolyRelated + class Main include Mongoid::Document - store_in collection: :poly_relateds - end - - class PolyMain - include Mongoid::Document - store_in collection: :poly_mains - belongs_to :related, polymorphic: true, optional: true end - class PolyTwo < PolyRelated - has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related + class Related + include Mongoid::Document + has_one :parent, as: :related end - class PolyThree < PolyRelated - has_one :parent, class_name: 'PolyMain', as: :related, inverse_of: :related - end + class Two < Related; end + class Three < Related; end end after(:context) do - %i[PolyMain PolyTwo PolyThree PolyRelated].each do |const| + %i[Main Related Two Three].each do |const| Object.send(:remove_const, const) if Object.const_defined?(const, false) end end it 'loads the related documents for each concrete type without raising' do - PolyMain.create!(related: PolyTwo.create!) - PolyMain.create!(related: PolyThree.create!) + Main.create!(related: Two.create!) + Main.create!(related: Three.create!) loaded = nil - expect { - loaded = PolyMain.includes(:related).entries - }.not_to raise_error - - expect(loaded.map { |doc| doc.related.class }).to match_array([PolyTwo, PolyThree]) - - expect { - PolyMain.last.related.id - }.not_to raise_error + expect { loaded = Main.includes(:related).entries }.not_to raise_error + expect(loaded.map { |doc| doc.related.class }).to match_array([Two, Three]) + expect { Main.last.related.id }.not_to raise_error end end @@ -131,15 +118,11 @@ class Main class Related include Mongoid::Document - end - - class Two < Related has_one :parent, as: :related end - class Three < Related - has_one :parent, as: :related - end + class Two < Related; end + class Three < Related; end RUBY init_script = base_script + <<~RUBY