I've developed a useful feature in KeystoneJS that lets you populate a relationship from either side, while only storing the data on one side, and am looking for feedback on whether it is something that could / should be brought back into mongoose itself. (It might be possible to add as a separate package but I suspect there'd be too much rewriting of mongoose internals for that to be a good idea).
I've added this as an issue in mongoose for consideration: #1888 but am leaving this gist in place because the examples are easier to read.
I've used Posts and Categories as a basic, contrived example to demonstrate what I'm talking about here; in reality you'd rarely load all the posts for a category but there are other real world cases where it's less unreasonable you'd want to do this, and Posts + Categories is an easy way to demo it.
The built-in population feature is really useful; not just for simple examples (like below) but if you're passing around a query (promise) in a sophisticated app, you can encapsulate both the query filters and the full dataset it should return, while allowing it to be modified by another method before it is run.
There are many ways to implement relationships between collections in mongo, one of the most performant (from a read perspective) being to store the relationship data on both models. Mongoose's population support also means this is one of the easiest to code against in many scenarios.
It requires a lot of management though; keeping arrays in sync means using pre/post save middleware, or piping any changes to the arrays through specific methods that keep them in sync for you.
See two-sided-relationship.js for an implementation of this (without the sync logic).
I think it's better to store relationships on one side only in many cases - either a single reference or an array of references on the primary Model. There's nothing to keep in sync, one 'source of truth' but it requires more code to query (from the secondary Model) and may not be as performant?
See one-sided-relationship.js for a (very rough) implementation of this.
I have developed a Relationship feature in Keystone that lets you populate one-sided relationships as if they were two-sided, by specifying a relationship
on the secondary schema, and propose we implement it (better) in mongoose itself.
In Keystone there is a (currently undocumented) populateRelated
method that is created on Lists
. See keystone-relationships.js for an example of how this works.
The populateRelated
method on Documents actually works in both directions, so it can populate (and sub-populate) nested relationships from either side, an example of that in use is outside the scope of the ones I've written, but it's very cool :)
The biggest downside to implementing it outside of mongoose's populate functionality is it can't be queued before the query is executed, so it has to be used similarly to the populate
method that is available to Document
objects. There's also a (currently horribly inefficient) method on keystone
itself to run this for all documents in an array.
If we brought this across, you could call a method on the mongoose Schema
telling it that another Schema holds a ref
to it in a path
(simple or array for one-to-many or many-to-many, could be detected from the related Schema). This relationship would then be taken into account by the populate
method, and (although the underlying queries would be different) it is treated like a path
storing { type: mongoose.Schema.Types.ObjectId, ref: '(primary Model)' }
.
See mongoose-relationships.js and mongoose-relationships-alt.js for how I propose this would work if implemented natively in mongoose.
If it's something that would be welcomed in mongoose I'd be happy to help implement it (but I've looked through the code and might need a primer by somebody who understands how populate
works better than I do). The method in Keystone is currently fairly rough; it works but the performance (and implementation) leaves quite a bit to be desired, whether it gets re-implemented in mongoose or stays a Keystone specific feature, I'd also love someone with more experience wrangling performance in mongoose to help me improve it.
The actual implementation in Keystone can be found here: