Package useful for mantaining the references integrity and structure of mongoose models. It provides cascade deleting, and ref support at any nested level. Also include support for soft deleting.
N.B: This is based on middleware hook remove and deleteOne of mongoose. If you would like to mantain the integrity anyway, you should always use this middleware even on a bunch of data (obviously at the cost of performance) by looping over the collection and deleting singularly every document.
If you are interested in the integrity of sub references too, watch this out.
Mongoose >= 5.10.7, MongoDB >= 3.6
For this package :
npm i mongoose-references-integrity-checker
If you would like to integrate it with soft deleting:
npm i mongoose-references-integrity-checker mongoose-soft-deleting
For setting up the integrity checker on a mongoose schema, you have two options:
const referencesIntegrityChecker = require('mongoose-references-integrity-checker');
const TestSchema = new mongoose.Schema({});
referencesIntegrityChecker('Test', TestSchema);
const TestModel = mongoose.model('Test', TestSchema);
const { consistentModel } = require('mongoose-references-integrity-checker');
const TestSchema = new mongoose.Schema({});
const TestModel = consistentModel('Test', TestSchema);
A reference could stay in three possible states:
- Required ( Deleting the parent of the relationship will throw an error )
- Required and Cascade ( Deleting the parent of the relationship will delete all of his children )
- Not required ( Deleting the parent will unset the ref on all of his children )
Setting up the models in this way :
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
// House - 1
const HouseSchema = new mongoose.Schema({});
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Only required
required: true,
},
});
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and child
const parent = await new HouseModel().save();
const child = await new RoomModel({ house: parent._id }).save();
try{
// Try delete
await parent.deleteOne();
}catch(e){
assert(e instanceof RefConstraintError);
}
Would lead in the situation in which when you delete the parent on the relationship (e.g. House) then will be thrown a RefConstraintError.
The reference is required on the child of the relationship, so you can't delete the parent without unsetting the reference first.
Consider this situation:
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
// House - 1
const HouseSchema = new mongoose.Schema({});
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Required and cascade
required: true,
cascade: true
},
});
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and children
const parent = await new HouseModel().save();
const child0 = await new RoomModel({ house: parent._id }).save();
const child1 = await new RoomModel({ house: parent._id }).save();
// Delete
await parent.deleteOne();
// All deleted
assert(!await HouseModel.findById(parent._id));
assert(!await RoomModel.findById(child0._id));
assert(!await RoomModel.findById(child1._id));
Deleting the parent of the relationship will delete all his children.
This is the last use case :
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
// House - 1
const HouseSchema = new mongoose.Schema({});
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Not required
required: false,
},
});
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and child
const parent = await new HouseModel().save();
const child = await new RoomModel({ house: parent._id }).save();
await parent.deleteOne();
// Ref on child will be null
assert(!child.house);
If the reference is not required then deleting the parent of the relationship will unset the ref on all his children.
In the last examples we've seen the most simple case, in which the ref on the child is in the root of the document. Any way you can nest it in the way you prefer and the usage will be the same.
const RoomSchema = new mongoose.Schema({
pathToRef: {
...
{
anyProp: [
...
propertyIfYouWant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
... (refOptions)
}
...
]
}
...
},
});
Optionally you can combine the usage of the library mongoose-soft-deleting with this package.
The behaviour in this case will be about the same with some differences.
If you try to soft delete the parent of the relationship, then will be thrown the same RefConstraintError as before.
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
const softDeletePlugin = require('mongoose-soft-deleting');
// House - 1
const HouseSchema = new mongoose.Schema({});
HouseSchema.plugin(softDeletePlugin);
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Only required
required: true,
},
});
RoomSchema.plugin(softDeletePlugin);
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and child
const parent = await new HouseModel().save();
const child = await new RoomModel({ house: parent._id }).save();
try{
// Try soft delete
await parent.softDelete(true);
}catch(e){
assert(e instanceof RefConstraintError);
}
If you try to soft delete or restore the parent of the relationship then all his children will have the same fate.
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
const softDeletePlugin = require('mongoose-soft-deleting');
// House - 1
const HouseSchema = new mongoose.Schema({});
HouseSchema.plugin(softDeletePlugin);
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Required and cascade
required: true,
cascade: true
},
});
RoomSchema.plugin(softDeletePlugin);
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and children
const parent = await new HouseModel().save();
const child0 = await new RoomModel({ house: parent._id }).save();
const child1 = await new RoomModel({ house: parent._id }).save();
// Delete
await parent.softDelete(true);
// All soft deleted
assert((await HouseModel.findById(parent._id)).isSoftDeleted());
assert((await RoomModel.findById(child0._id)).isSoftDeleted());
assert((await RoomModel.findById(child1._id)).isSoftDeleted());
// Restore
await parent.softDelete(false);
// All restored
assert(!((await HouseModel.findById(parent._id)).isSoftDeleted()));
assert(!((await RoomModel.findById(child0._id)).isSoftDeleted()));
assert(!((await RoomModel.findById(child1._id)).isSoftDeleted()));
If you try to soft delete the parent of the relationship then only the parent will be soft deleted. The child will still have his reference set to the parent (because even if the parent is soft deleted, it still exists).
const { consistentModel, RefConstraintError } = require('mongoose-references-integrity-checker');
const softDeletePlugin = require('mongoose-soft-deleting');
// House - 1
const HouseSchema = new mongoose.Schema({});
HouseSchema.plugin(softDeletePlugin);
const HouseModel = consistentModel('House', HouseSchema);
// Room - N
const RoomSchema = new mongoose.Schema({
house: {
type: mongoose.Schema.Types.ObjectId,
ref: 'House',
// Not required
required: false,
},
});
RoomSchema.plugin(softDeletePlugin);
const RoomModel = consistentModel('Room', RoomSchema);
...
// Setup parent and child
const parent = await new HouseModel().save();
const child = await new RoomModel({ house: parent._id }).save();
await parent.deleteOne();
// Ref on child will be the same
assert(child.house.equals(parent._id));
You can try the tests using the following command ( before you need to change the connection to MongoDB ) :
npm install --test
npm run test
If you would like to support my work, please buy me a coffe ☕. Thanks in advice.