Dynamic ObjectID ref for a subdocument inside an array in the parent document

69 views Asked by At

I'm trying to create a dynamic ref for an object ID stored inside a property of a subdocument. The property can reference multiple models (even models registered in a different mongoose database instance) so I use the model instance directly instead of a model name.

const subSchema = new Schema({
    category: String,
    ref: {
        type: Schema.Types.ObjectId,
        ref: function() {
            return this.category === 'catA' ? CategoryAModel : CategoryBModel // imports model instances
        }
    }
})

const parentSchema = new Schema({
    name: String,
    children: [subSchema]
})

const ParentModel = db.model('Parent', parentSchema)

As you can see, the ref property is the Object ID of either a document from CategoryAModel or CategoryBModel. I started creating documents for this model, such as the following:

const data = {
    name: 'Test Data',
    children: [
        {
            category: 'catA',
            ref: '658e9f1901f3da2c14920401' // <- existing document from CategoryAModel
        },
        {
            category: 'catB',
            ref: '654995e0c89d1c19c84e77b7' // <- existing document from CategoryBModel
        }
    ]
}

But when i tried to populate this, the ref for category: 'catA' becomes null (despite existing). I logged the this context in the ref's ref function and saw that this refers to the document being processed (same shape as the data above), and that this.category is undefined because it's actually inside the children array. Essentially making the ref always result in being the CategoryBModel

Since it's an array, how would I go and make a dynamic reference? Is there a way to access the index of the subSchema being referred to?

1

There are 1 answers

2
jQueeny On

You should use refPath instead. Mongoose has designed the populate method to use the refPath option for dynamic models and will meet your needs.

Use it like so:

const subSchema = new Schema({
    category: String,
    ref: {
        type: Schema.Types.ObjectId,
        refPath: 'children.classifier', //< refPath should point to classifier
    },
    classifier: {
       type: String,
       enum: ['CategoryAModel', 'CategoryBModel'] //< The name of your models
    }
})

Now when you save your Parent documents the children objects should have which model you want to populate with stored in that classifier string. Your data will look like:

const data = {
   name: 'Test Data',
   children: [
      {
         category: 'catA',
         ref: ObjectId("658e9f1901f3da2c14920401"), // <- existing document from CategoryAModel
         classifier: 'CategoryAModel'
      },
      {
         category: 'catB',
         ref: ObjectId("654995e0c89d1c19c84e77b7"), // <- existing document from CategoryBModel
         classifier: 'CategoryBModel'
      }
   ]
}

Now you can populate like so:

const parents = await ParentModel.find({}).populate("children.ref");