Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate Virtuals do not work with Discriminators #10082

Closed
jonathan-wilkinson opened this issue Mar 30, 2021 · 1 comment
Closed

Populate Virtuals do not work with Discriminators #10082

jonathan-wilkinson opened this issue Mar 30, 2021 · 1 comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Milestone

Comments

@jonathan-wilkinson
Copy link
Contributor

jonathan-wilkinson commented Mar 30, 2021

Issue: Bug
Behaviour:

Populate Virtuals do not work when there is:

  • a model with a discriminator
  • the model being "populated" is not an explicitly defined sub-type

Reproduction

This script creates a models for Animal and Food. All animals have a type (cat | rabbit). All animals have a virtual that populates the Food they eat. An Animal of kind "cat" has a custom catYears property. An Animal of kind "rabbit" has no rabbit-specific props.

A cat and a rabbit are populated along with food for each of them. Both are then fetched from the DB, populating the foods they eat. The result is logged.

The expectation is that the foods for both are populated. However, it is only populated for the cat (which has a discriminator set up). If we add a discriminator for "rabbit", we will see it is populated for both.

// package.json
{
  "dependencies": {
    "mongodb-memory-server": "^6.9.6",
    "mongoose": "^5.12.0"
  }
}
// index.js
// @ts-check
const mongoose = require("mongoose")
const { MongoMemoryServer} = require("mongodb-memory-server");

(async () => {
    /**
     * SETUP DATABASE
     */
    const mongod = new MongoMemoryServer();
    const uri = await mongod.getUri();

    /**
     * CONNECT MONGOOSE
     */
    await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });

    /**
     * CREATE SCHEMAS
     */
    const foodSchema = new mongoose.Schema({
        name: String,
        animal: String
    });
    const Food = mongoose.model('Food', foodSchema);

    const animalSchema = new mongoose.Schema({
        type: String,
    }, {
        discriminatorKey: 'type',
        toJSON: { virtuals: true }, 
        toObject: { virtuals: true } 
    });
    const catSchema = new mongoose.Schema({
        catYears: { type: Number, required: true }
    });
    animalSchema.virtual('foods', {
        ref: 'Food',
        localField: 'type',
        foreignField: 'animal',
        justOne: false
    });
    const Animal = mongoose.model('Animal', animalSchema);
    const Cat = Animal.discriminator('cat', catSchema)
        
    /** 
     * TEST
     */
    await Promise.all([
        new Food({ name: 'Cat Food', animal: 'cat' }).save(),
        new Food({ name: 'Rabbit Food', animal: 'rabbit' }).save(),
    ]);
    await new Animal({ type: 'cat', catYears: 4 }).save();
    await new Animal({ type: 'rabbit' }).save(); // <-- "rabbit" has no discriminator 

    const cat = await Animal.findOne({ type: 'cat' }).populate('foods');
    const rabbit = await Animal.findOne({ type: 'rabbit' }).populate('foods');

    /**
     * Expect: Both cat and rabbit have "foods"
     * Result: Only cat has "foods"
     */
    console.log(JSON.stringify({ cat, rabbit }, undefined, 2));

})()
.catch(err => console.error(err))
.then(() => process.exit())

Expected Behaviour

Populate Virtuals work irrespective of if a specific instance of a model has specific sub-type defined.

Use Cases

The use cases for this are where there are models with a number of variants, but only a small number of those variants have variant-specific behaviour.

Versions

Node: v12.21.0 / v14.8.0
Mongoose: 5.12.2

@IslandRhythms IslandRhythms added the confirmed-bug We've confirmed this is a bug in Mongoose and will fix it. label Apr 7, 2021
@IslandRhythms
Copy link
Collaborator

// index.js
// @ts-check
const mongoose = require("mongoose")
const { MongoMemoryServer} = require("mongodb-memory-server");

(async () => {
    /**
     * SETUP DATABASE
     */
    const mongod = new MongoMemoryServer();
    const uri = await mongod.getUri();

    /**
     * CONNECT MONGOOSE
     */
    await mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true });

    /**
     * CREATE SCHEMAS
     */
    const foodSchema = new mongoose.Schema({
        name: String,
        animal: String
    });
    const Food = mongoose.model('Food', foodSchema);

    const animalSchema = new mongoose.Schema({
        type: String,
    }, {
        discriminatorKey: 'type',
        toJSON: { virtuals: true }, 
        toObject: { virtuals: true } 
    });
    const catSchema = new mongoose.Schema({
        catYears: { type: Number, required: true }
    });
    const rabbitSchema = new mongoose.Schema({
        rabbitYear: {type: Number, required: true}
    });
    animalSchema.virtual('foods', {
        ref: 'Food',
        localField: 'type',
        foreignField: 'animal',
        justOne: false
    });
    const Animal = mongoose.model('Animal', animalSchema);
    const Cat = Animal.discriminator('cat', catSchema)
    //const Rabbit = Animal.discriminator('rabbit', rabbitSchema);
        
    /** 
     * TEST
     */
    await Promise.all([
        new Food({ name: 'Cat Food', animal: 'cat' }).save(),
        new Food({ name: 'Rabbit Food', animal: 'rabbit' }).save(),
    ]);
    await new Animal({ type: 'cat', catYears: 4 }).save();
    await new Animal({ type: 'rabbit'/*, rabbitYear: 3 */}).save(); // <-- "rabbit" has no discriminator 

    const cat = await Animal.findOne({ type: 'cat' }).populate('foods');
    const rabbit = await Animal.findOne({ type: 'rabbit' }).populate('foods');

    /**
     * Expect: Both cat and rabbit have "foods"
     * Result: Only cat has "foods"
     */
    console.log(JSON.stringify({ cat, rabbit }, undefined, 2));

})()
.catch(err => console.error(err))
.then(() => process.exit())

@vkarpov15 vkarpov15 added this to the 5.12.5 milestone Apr 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug We've confirmed this is a bug in Mongoose and will fix it.
Projects
None yet
Development

No branches or pull requests

3 participants