Using dynamic references with Node.js and mongoose ‘refPath’

While doing a bit of refactoring lately, I decided to re-design an old schema using nested “dynamic refs” instead of normal refs with server-side logic checks. After all, it seemed like the best way to avoid the crazy conditional checks I was running every time I fetched data.
My original schema was this:
My log schema was this:
Where I would leave task
as undefined
if I was tracking a workOrder
or viceversa.
That, of course, gets messy when populating and displaying data so naturally I wanted to figure out a way to make use of dynamic refs.
The wrong way to do it.
Upon reading the mongoose
docs, I assumed I could get away with doing the following:
While this looks elegant, when we call model.find().populate('session.firstAcitivity.logId')
, mongoose
will spit out an error about not being able to find the model type to use for the populate action.
This is because in the new logSchema
we defined above, we are telling mongoose that the refPath
for logId
will be resolved via logType
. However, when we nest that schema somewhere else, that refPath is no longer valid.
Mongoose needs an absolute and unambiguous string to resolve refPath!
[TL;DR] The right way to do it.
In fact, what we have to do is write our schema the following way:
While this looks more verbose, our populate logic is very straightforward now; a simple query
model.
find().
populate('session.firstActivity.logId').
exec();
will unambiguously fetch the data we want.
And it works with arrays, too! populate('session.activities.logId')
instead, will give us the populated documents just as expected.
A closing note about using refPath. While it is a very powerful tool, it requires defining additional models (those defined in enum: ['taskLog', 'workOrderLog']
). The trade-off, of course, is more readable code, if used properly.