mongoose icon indicating copy to clipboard operation
mongoose copied to clipboard

Dynamic virtual populate not working on subdocument with separate schema

Open Hybrid-Force opened this issue 3 years ago • 3 comments

Prerequisites

  • [X] I have written a descriptive issue title
  • [X] I have searched existing issues to ensure the bug has not already been reported

Mongoose version

6.5.4

Node.js version

16.15.0

MongoDB server version

4.2.1

Description

Seems to be related to #8277 and #8742

When using dynamic ref on child schema { ref: (doc) => doc.refType }, the passed in doc is the parent document, not the subdocument, causing dynamic ref path not correctly returned.

I tried both virtuals option in schema constructor options and schema virtual() method, tried both ref and refPath in virtual options, none works.

Steps to Reproduce

import mongoose from 'mongoose';
import { expect } from 'chai';

const nestedSchema = new mongoose.Schema({
    targetType: String,
    targetId: mongoose.Schema.Types.ObjectId,
}, {
    virtuals: {
        target: {
            options: {
                ref: (doc: any) => {
                    // doc should be the sub document
                    // but instead, it is the parent document
                    console.debug(doc);
                    return doc.targetType;
                },
                localField: 'targetId',
                foreignField: '_id',
                justOne: true,
            }
        }
    }
});

const parentSchema = new mongoose.Schema({
    nested: {
        type: nestedSchema
    },
});

const NestedDynamicVirtualPopulateTest = mongoose.model('NestedDynamicVirtualPopulateTest', parentSchema);

await NestedDynamicVirtualPopulateTest.collection.drop();
const target = new NestedDynamicVirtualPopulateTest({});
await target.save();

const parent = new NestedDynamicVirtualPopulateTest({
    nested: {
        targetType: (target.constructor as typeof NestedDynamicVirtualPopulateTest).modelName,
        targetId: target._id,
    }
});
await parent.save();
await parent.populate('nested.target');
// @ts-ignore
expect(parent.nested.target).to.exist;

Expected Behavior

Since the virtual is defined on the child schema, the dynamic ref path should be relative to the subdocument, not the parent document.

Hybrid-Force avatar Aug 31 '22 08:08 Hybrid-Force

image

12363.ts:49:29 - error TS2551: Property 'target' does not exist on type '{ targetType?: {}; targetId?: { toString: any; _bsontype?: { toString: {}; _bsontype?: ObjectId; inspect?: {}; equals?: {}; _id?: ...; id?: { [x: number]: unknown; [toStringTag]?: unknown; [iterator]?: {}; toString: {}; ... 100 more ...; byteOffset?: unknown; }; toHexString?: {}; toJSON?: {}; generationTime?: unknow...'. Did you mean 'targetId'?

49   console.log(parent.nested.target);
                               ~~~~~~

node_modules/mongoose/types/query.d.ts:619:29 - error TS2304: Cannot find name 'this'.

619     toConstructor(): typeof this;
                                ~~~~


Found 2 errors.
import mongoose from 'mongoose';

const nestedSchema = new mongoose.Schema({
    targetType: String,
    targetId: mongoose.Schema.Types.ObjectId,
}, {
    virtuals: {
        target: {
            options: {
                ref: (doc: any) => {
                    // doc should be the sub document
                    // but instead, it is the parent document
                    console.debug(doc);
                    return doc.targetType;
                },
                localField: 'targetId',
                foreignField: '_id',
                justOne: true,
            }
        }
    }
});

const parentSchema = new mongoose.Schema({
    nested: {
        type: nestedSchema
    },
});

const NestedDynamicVirtualPopulateTest = mongoose.model('NestedDynamicVirtualPopulateTest', parentSchema);
async function run() {
  await mongoose.connect('mongodb://localhost:27017');
  await mongoose.connection.dropDatabase();
  const target = new NestedDynamicVirtualPopulateTest({});
  await target.save();

  const parent = new NestedDynamicVirtualPopulateTest({
      nested: {
          targetType: (target.constructor as typeof NestedDynamicVirtualPopulateTest).modelName,
          targetId: target._id,
      }
  });
  await parent.save();
  await parent.populate('nested.target');
  console.log(parent);
  console.log('=============');
  console.log(parent.nested);
  console.log('=================');
  console.log(parent.nested.target);
  console.log('======================')
}

run();

IslandRhythms avatar Aug 31 '22 15:08 IslandRhythms

Hi @IslandRhythms, the code snippet in my initial comment was not properly typed, because I thought the type definition does not do much help demonstrating the issue and will make the snippet unnecessarily long. So I just put @ts-ignore before the parent.nested.target part to avoid typing errors :)

Hybrid-Force avatar Aug 31 '22 16:08 Hybrid-Force

Hi @IslandRhythms, I think the "typescript" label on this issue is not appropriate. The typing error in your comment is not related to this issue. And this issue is not related to typescript.

Hybrid-Force avatar Sep 16 '22 07:09 Hybrid-Force