mongoose: Populating a discriminated subdocument
Asked Answered
P

1

7

I want to populate the fields of a subdocument, which is a discriminated element of a common Schema (Notificationable discriminated into Message or FriendRequest).

This question is quite similar to this one: mongoosejs: populating an array of objectId's from different schemas, which was not solved two years ago. Since mongoose evolved, and discriminator also, I am asking the question again.

What I have tried so far:

Notification.find({_id: 'whatever'})
    .populate({
        path: 'payload',
        match: {type: 'Message'},
        populate: ['author', 'messageThread']
    })
    .populate({
        path: 'payload',
        match: {type: 'FriendRequest'},
        populate: ['to', 'from']
    })
    .exec();

This does not work, because the path is the same. So I tried:

Notification.find({_id: 'whatever'})
    .populate({
        path: 'payload',
        populate: [
            {
                path: 'messageThread',
                match: {type: 'Message'},
            },
            {
                path: 'author',
                match: {type: 'Message'},
            },
            {
                path: 'from',
                match: {type: 'FriendRequest'},
            },
            {
                path: 'to',
                match: {type: 'FriendRequest'},
            },

        ]
    })
    .exec();

Which does not work either, maybe because the match is executed in the subdocument and thus does not have a field type.

Is there any solution for this?


Here are my (main) models, I did not provide User or MessageThread.

The main document:

const NotificationSchema = new Schema({
    title: String,
    payload: {
        type: Schema.Types.ObjectId,
        ref: 'Notificationable'
    });
mongoose.model('Notification', NotificationSchema);

The payload Parent schema

let NotificationableSchema = new Schema(
    {},
    {discriminatorKey: 'type', timestamps: true}
);
mongoose.model('Notificationable', NotificationableSchema);

And the two discriminated possibilities:

let Message = new Schema({
    author: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    messageThread: {
        type: Schema.Types.ObjectId, 
        ref: 'MessageThread'
    }
}
Notificationable.discriminator('Message', Message);

And:

let FriendRequest = new Schema({
    from: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    to: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    }
}
Notificationable.discriminator('FriendRequest', FriendRequest);
Parsnip answered 8/11, 2017 at 10:33 Comment(1)
For someone using the mongoose-autopopulate plugin, it "is based entirely on pre('find') and pre('findOne') hooks, which don't get fired for the child model when you do find() on the parent model." reference: github.com/mongodb-js/mongoose-autopopulate/issues/26Unpin
D
1

I have tried this with a similar problem I faced and I think it can help in your scenario

Notifiable.findOne({_id: 'whatever'})
    .populate({
        path: 'payload',
        populate: { path: '__t' }, // Populating the discriminator key
        model: function(doc) {
            // Choose the appropriate discriminator model based on the value of the discriminator key
            switch (doc.payload.__t) {
                case 'Message':
                    return Message;
                case 'FriendRequest':
                    return FriendRequest;
                default:
                    throw new Error('Invalid discriminator value');
            }
        }
    })

You should also import your models in the same file you are calling this snippet whether your controller or service file

Daddylonglegs answered 5/4 at 19:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.