Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/mongoid/includes/inclusion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
129 changes: 129 additions & 0 deletions spec/mongoid/includes/polymorphic_includes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,134 @@
}.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
require 'open3'

database_name = "mongoid_includes_spec_#{SecureRandom.hex(6)}"
Comment thread
danarnold marked this conversation as resolved.
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
Comment thread
danarnold marked this conversation as resolved.
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

bundle_gemfile = ENV.fetch(
'BUNDLE_GEMFILE',
File.join(project_root, 'Gemfile')
)

run_script = lambda do |script|
Open3.capture2e(
{ 'BUNDLE_GEMFILE' => bundle_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