When you search for referential integrity in relation to Mongo-DB the standard response is "MongoDB does not support this". The standard explanation is that MongoDB supports refs and populate, however there is nothing that prevents you changing the ref to an invalid value. This is a major deterrent for many developers coming from a SQL background.
In relation to referential integrity on deletes, provided all delete requests are served by your application then it can be handled by checking ID does not exists in related collections prior to deleting records. I do this as follows
CRUD Operations (We are only concerned with Delete here - note how I am passing an array of objects being the collection and field that needs to be matched against ID of document (record) we are deleting
const express = require('express')
const router = express.Router()
const iflexCRUD = require('../../lib/iflexCRUD')
const { UnitType } = require('../../models/Unittype')
const { Unit } = require('../../models/Unit')
iflexCRUD.create(router, '/', UnitType)
iflexCRUD.read(router, '/', UnitType, { sort: 'name' })
iflexCRUD.update(router, '/:id', UnitType)
iflexCRUD.deleteByID(router, '/:id', UnitType, [
{
model: Unit,
field: 'unittype'
}
])
iflexCRUD.delete(router, '/unittype/:unittype', UnitType)
module.exports = router
CRUD Delete Handler This is a generic delete request handler that I use for CRUD operations I passes an array of Collection / Field values and checks to see if there is a single record that matches the ID of the document being deleted.
// CRUD-DELETE
iflexCRUD.deleteByID = (router, route, Collection, refs = []) => {
router.delete(route, async (req, res) => {
try {
let exception = false
//Enforce Referential Integrity for deletes - Deny when ID is used in any of refs collections
//Loop through any referenced files (first record) to ensure there are no other collections using this document
for (let i = 0; i < refs.length; i++) {
if (!exception) {
let refObj = {}
refObj[refs[0].field] = req.params.id
const result = await refs[i].model.findOne(refObj, (err, rec) => {})
exception = result !== null
}
}
// Process deletion of there are no exceptions
if (!exception) {
const doc = await Collection.deleteOne({ _id: req.params.id })
res.send(doc)
} else {
return res
.status(401)
.json(
'Document is already use in related collection - it cannot Delete!'
)
}
} catch (e) {
return res.status(401).json(e.message)
}
})
}
THERE IS A SOLUTION!
Sure - there is no way to prevent someone changing a reference through Compass or another GUI but there is definitely a way to do it when you are developing your own API and in control of all read and write operations to the database.
I will illustrate with an example from some code I am currently working on. Firstly the UnitType.js defines a very simple collection with simply a name and an ID. The magic is in Unit.js - Have a look at the custom validator for unittype field. It issues an asynch request to the UnitType model to find the related UnitType by its _id field. If the ID provided on save or update is invalid, an error message will be returned "Invalid Object ID" otherwise record will be saved. In this way it is never possible to create or modify a record with an invalid UnitType record.
This process can be repeated if there are multiple references.
Provided you are using the same model throughout your application, there is no need to write additional code to support referential integrity
I hope this helps
// UnitType.js - MongoDB Schema
const mongoose = require('mongoose')
const UnitType = mongoose.model(
'UnitType',
new mongoose.Schema(
{
name: {
type: String,
required: true,
minlength: 5,
maxlength: 40
}
},
{ collection: 'unittype' }
)
)
exports.UnitType = UnitType
// Unit.js - MongoDB Schema
const mongoose = require('mongoose')
const { UnitType } = require('./Unittype')
const Unit = mongoose.model(
'Unit',
new mongoose.Schema(
{
door: {
type: String,
required: true,
minlength: 2,
maxlength: 10,
index: true,
unique: true
},
name: {
type: String,
required: true,
minlength: 5,
maxlength: 40
},
location: {
type: String
},
description: {
type: String
},
unittype: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UnitType',
// Ensure that UnitType iD is valid (note isAsync is deprecated)
validate: {
validator: async function(v) {
return await UnitType.findById(v, (err, rec) => rec !== null)
},
message: 'Invalid Object ID'
}
}
},
{ collection: 'unit' }
)
)
exports.Unit = Unit
© 2022 - 2024 — McMap. All rights reserved.