Alternate populate/save/delete functionality for mongoose to allow for cascading relations
This is a Mongoose plugin that adds cascading save and remove functionality for defined relationships between documents.
I created this plugin for two reasons.
One, I found it increasingly annoying to convert populated documents back into their original form when communicating back and forth between client and server. Mongoose's
populate method is awesome, but when sending back, I had to convert the populated data back to its
Two, I wanted an easy way to handle actual ORM using Mongoose. Mongoose bills itself as an ORM but the only actual relationship mapping it does is with its
ObjectID type and the
This plugin adds a special field to Mongoose schemas called
_related. All document relationships are run through this field.
When you run the
populate method, instead of populating by replacing the
ObjectID references, it populates on the exact path but within the
_related field. This gives you access to related documents while keeping the actual database fields intact for write operations. For example:
mySchemafindpopulate'foo'exec// The "title" attribute of the related fooconsole.logresults0_relatedfootitle;// The _id field of the related fooconsole.logresults0foo;
As with the Mongoose core, related documents are specified with a combination of
ref:'Related_Model'. This plugin adds two more configuration options to
ObjectID types: $through and $cascadeDelete.
$through defines the path on the related document that is a reference back to this document. If you have two schema like so:
var cascadingRelations = require'cascading-relations';var fooSchema =title:Stringbars:type:mongooseSchemaTypesObjectIdref:'Bar'$through:'foo';// Apply the pluginfooSchemaplugincascadingRelations;var barSchema =title:Stringfoo:type:mongooseSchemaTypesObjectIdref:'Foo';// Apply the pluginbarSchemaplugincascadingRelations;
foo property of each related
bar will be populated with the
_id field of the
$cascadeDelete defines whether or not deleting a document will also delete its related documents. If this is set to
true, then all related documents will be deleted when the main document is deleted. If it is
false or undefined, then only the
$through field of related documents will be nullified (if it's a single relationship) or the removed document's
_id will be removed from the
$through field (if it's a multi relationship). The related document(s) will remain in the database.
You can create related documents in one model by using aforementioned the
var fooModel = mongoosemodel'Foo'var myFoo =title:'My Foo'_related:bars:title:'My First Bar'title:'My Second Bar'single_bar:title:'My Single Bar';
IMPORTANT! When you save, you need to use
cascadeSave()will update all reference fields on the main document and all related documents, cascade through infinite levels of related documents, and update the corresponding
_relatedfields with the saved documents (including
__v, etc). Using the
save()method will save using the Mongoose core - anything set in the
_relatedfield will not be saved (although it will still be accessible after the DB write operation)
Because Mongoose still does not have callbacks implemented in its
post middleware, all cascade delete operations happen behind the scenes after the callback is executed for the initial
remove() call. Because of this, if you query the database immediately after running
remove(), the cascade delete processes still may not have finished. In our tests, we get around this by simply waiting 5 seconds before checking if the process was successful. Keep this in mind when using this plugin - unfortunately there is nothing we can really do except for writing a custom middleware handler.
Currently if saving one related document fails (for validation or another reason), it bubbles up all the way to the top and stops the saving of the original document. I'm considering adding some configuration options to ignore these issues and continue with the save process.