mongoose-references-integrity-checker
v1.0.9
Published
Package useful for mantaining the references structure of mongoose models. Supporting soft delete.
Downloads
10
Maintainers
Readme
Package: mongoose-references-integrity-checker
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.
Dependencies
Mongoose >= 5.10.7, MongoDB >= 3.6
Install
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
Setup
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);
Concepts
Reference States
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 )
Required
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.
Required and Cascade
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.
Not Required
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.
Nesting
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)
}
...
]
}
...
},
});
Soft Delete
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.
Required
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);
}
Required and cascade
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()));
Not required
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));
Test
You can try the tests using the following command ( before you need to change the connection to MongoDB ) :
npm install --test
npm run test
Support
If you would like to support my work, please buy me a coffe ☕. Thanks in advice.