Reputation: 8607
Is there a way to delete all children of an parent in Mongoose, similar to using MySQLs foreign keys?
For example, in MySQL I'd assign a foreign key and set it to cascade on delete. Thus, if I were to delete a client, all applications and associated users would be removed as well.
From a top level:
Sweepstakes and submissions both have a field for client_id. Submissions has a field for both sweepstakes_id, and client_id.
Right now, I'm using the following code and I feel that there has to be a better way.
Client.findById(req.params.client_id, function(err, client) {
if (err)
return next(new restify.InternalError(err));
else if (!client)
return next(new restify.ResourceNotFoundError('The resource you requested could not be found.'));
// find and remove all associated sweepstakes
Sweepstakes.find({client_id: client._id}).remove();
// find and remove all submissions
Submission.find({client_id: client._id}).remove();
client.remove();
res.send({id: req.params.client_id});
});
Upvotes: 62
Views: 58298
Reputation: 37
As with the Mongoose core, related documents are specified with a combination of type:mongoose.Schema.Types.ObjectId and 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 = new mongoose.Schema({ title:String, bars:[{ type:mongoose.Schema.Types.ObjectId, ref:'Bar', $through:'foo' }] });
// Apply the plugin fooSchema.plugin(cascadingRelations);
var barSchema = new mongoose.Schema({ title:String, foo:{ type:mongoose.Schema.Types.ObjectId, ref:'Foo' } });
// Apply the plugin barSchema.plugin(cascadingRelations);
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.
Upvotes: 0
Reputation: 631
Model
const orderSchema = new mongoose.Schema({
// Множество экземпляров --> []
orderItems: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'OrderItem',
required: true
}],
...
...
});
asyncHandler (optional)
const asyncHandler = fn => (req, res, next) =>
Promise
.resolve(fn(req, res, next))
.catch(next)
module.exports = asyncHandler;
controller
const asyncHandler = require("../middleware/asyncErrHandler.middleware");
// **Models**
const Order = require('../models/order.mongo');
const OrderItem = require('../models/order-item.mongo');
// @desc Delete order
// @route DELETE /api/v1/orders/:id
// @access Private
exports.deleteOrder = asyncHandler(async (req, res, next) => {
let order = await Order.findById(req.params.id)
if (!order) return next(
res.status(404).json({ success: false, data: null })
)
await order.remove().then( items => {
// Cascade delete -OrderItem-
items.orderItems.forEach( el => OrderItem.findById(el).remove().exec())
}).catch(e => { res.status(400).json({ success: false, data: e }) });
res.status(201).json({ success: true, data: null });
});
https://mongoosejs.com/docs/api/model.html#model_Model-remove
Upvotes: 0
Reputation: 832
I noticed that all of answers here have a pre
assigned to the schema and not post
.
my solution would be this: (using mongoose 6+)
ClientSchema.post("remove", async function(res, next) {
await Sweepstakes.deleteMany({ client_id: this._id });
await Submission.deleteMany({ client_id: this._id });
next();
});
By definition post gets executed after the process ends pre => process => post
.
Now, you're probably wondering how is this different than the other solutions provided here.
What if a server error or the id of that client was not found?
On pre, it would delete all sweeptakes
and submissions
before the deleting process start for client
. Thus, in case of an error, it would be better to cascade delete the other documents once client
or the main document gets deleted.
async and await are optional here. However, it matters on large data. so that the user wouldn't get those "going to be deleted" cascade documents data if the delete progress is still on.
At the end, I could be wrong, hopefully this helps someone in their code.
Upvotes: 4
Reputation: 311855
This is one of the primary use cases of Mongoose's 'remove'
middleware.
clientSchema.pre('remove', function(next) {
// 'this' is the client being removed. Provide callbacks here if you want
// to be notified of the calls' result.
Sweepstakes.remove({client_id: this._id}).exec();
Submission.remove({client_id: this._id}).exec();
next();
});
This way, when you call client.remove()
this middleware is automatically invoked to clean up dependencies.
Upvotes: 142
Reputation: 1812
Here's an other way I found
submissionSchema.pre('remove', function(next) {
this.model('Client').remove({ submission_ids: this._id }, next);
next();
});
Upvotes: 2
Reputation: 4619
In case your references are stored other way around, say, client
has an array of submission_ids
, then in a similar way as accepted answer you can define the following on submissionSchema
:
submissionSchema.pre('remove', function(next) {
Client.update(
{ submission_ids : this._id},
{ $pull: { submission_ids: this._id } },
{ multi: true }) //if reference exists in multiple documents
.exec();
next();
});
which will remove the submission's id from the clients' reference arrays on submission.remove()
.
Upvotes: 12