How can I populated nested documents with their Virtuals with Mongoose?

56 Views Asked by At

I have following schema:

// foo.js
...

const fooSchema = new Schema(
  {
    name: {
      type: String
    }
  },
  {
    toJSON: {
      virtuals: true
    },
    toObject: {
      virtual: true
    }
  }
);

// sample virtual
fooSchema.virtual('sampleVirtual').get(function () {
  return 'I am a virtual'
});

export default mongoose.model( 'foo', fooSchema );

and another schema where I have a ref on the above schema:

// hello.js
...

const helloWorldSchema = new Schema(
  {
    name: {
      type: String,
      required: true
    },
    myFoo: {
      type: mongoose.Types.ObjectId,
      ref: 'foo'
    }
  },
  {
    toJSON: { 
      virtuals: true 
    },
    toObject: {
        virtual: true
    }
  }
);

export default mongoose.model( 'HelloWorld', helloWorldSchema );

I am now using Mongoose' findOne() method to query for a document of the helloWorldSchema collection and want to populate the myFoo document with it's virtual sampleVirtual:

// get.js
const retVal = await helloWorldModel
    .findOne({_id: <some-id> })
    .populate({ path: 'myFoo', select: '_id name sampleVirtual' })
    .exec();

console.log(retVal.myFoo.sampleVirtual) //undefined

Am I doing something wrong?

Best Valentin

1

There are 1 best solutions below

1
Lin Du On BEST ANSWER

Your code has some typos. The toJSON and toObject are schema options, and the options is the second parameter of the mongoose.Schema class constructor. The mongoose.Schema class constructor signature is:

constructor(definition?: SchemaDefinition<LeanDocument<SchemaDefinitionType>>, options?: SchemaOptions);

E.g. ("mongoose": "^7.3.1")

import mongoose from 'mongoose';
import { config } from '../../config';

mongoose.set('debug', true);

const fooSchema = new mongoose.Schema(
    {
        name: String,
    },
    {
        toJSON: { virtuals: true },
        toObject: { virtuals: true },
    },
);
fooSchema.virtual('sampleVirtual').get(function () {
    return 'I am a virtual';
});
const Foo = mongoose.model('foo', fooSchema);

const helloWorldSchema = new mongoose.Schema(
    {
        name: String,
        myFoo: { type: mongoose.Schema.Types.ObjectId, ref: 'foo' },
    },
    {
        toJSON: { virtuals: true },
        toObject: { virtuals: true },
    },
);
const HelloWorld = mongoose.model('helloWorld', helloWorldSchema);

(async function main() {
    try {
        await mongoose.connect(config.MONGODB_URI);
        // seed
        const [foo1] = await Foo.create([{ name: 'a' }, { name: 'b' }]);
        const [h1] = await HelloWorld.create([
            { name: 'x', myFoo: foo1._id },
            { name: 'y', myFoo: foo1._id },
        ]);
        const retVal = await HelloWorld.findOne({ _id: h1._id })
            .populate({ path: 'myFoo', select: '_id name sampleVirtual' })
            .exec();
        console.log(retVal);
    } catch (error) {
        console.error(error);
    } finally {
        await mongoose.connection.close();
    }
})();

Debug logs:

Mongoose: foos.insertOne({ name: 'a', _id: ObjectId("649a7bdfbedaa852fa187538"), __v: 0 }, {})
Mongoose: foos.insertOne({ name: 'b', _id: ObjectId("649a7bdfbedaa852fa187539"), __v: 0 }, {})
Mongoose: helloworlds.insertOne({ name: 'x', myFoo: ObjectId("649a7bdfbedaa852fa187538"), _id: ObjectId("649a7bdfbedaa852fa18753c"), __v: 0}, {})
Mongoose: helloworlds.insertOne({ name: 'y', myFoo: ObjectId("649a7bdfbedaa852fa187538"), _id: ObjectId("649a7bdfbedaa852fa18753d"), __v: 0}, {})
Mongoose: helloworlds.findOne({ _id: ObjectId("649a7bdfbedaa852fa18753c") }, {})
Mongoose: foos.find({ _id: { '$in': [ ObjectId("649a7bdfbedaa852fa187538") ], [Symbol(mongoose#trustedSymbol)]: true }}, { skip: undefined, limit: undefined, perDocumentLimit: undefined, projection: { _id: 1, name: 1, sampleVirtual: 1 }})
{
  _id: new ObjectId("649a7bdfbedaa852fa18753c"),
  name: 'x',
  myFoo: {
    _id: new ObjectId("649a7bdfbedaa852fa187538"),
    name: 'a',
    sampleVirtual: 'I am a virtual',
    id: '649a7bdfbedaa852fa187538'
  },
  __v: 0,
  id: '649a7bdfbedaa852fa18753c'
}

The virtual field sampleVirtual is there.