Let's suppose I have a schema like this:

var Person = new Schema({
    name: String
});

var Assignment = new Schema({
    name: String,
    person: ObjectID
});

If I delete a person, there can still be orphaned assignments left that reference a person that does not exist, which creates extraneous clutter in the database.

Is there a simple way to ensure that when a person is deleted, all corresponding references to that person will also be deleted?

Solution 1

You can add your own 'remove' Mongoose middleware on the Person schema to remove that person from all other documents that reference it. In your middleware function, this is the Person document that's being removed.

Person.pre('remove', function(next) {
    // Remove all the assignment docs that reference the removed person.
    this.model('Assignment').remove({ person: this._id }, next);
});

Solution 2

If by "simple" you mean "built-in", then no. MongoDB is not a relational database after all. You need to implement your own cleaning mechanism.

Solution 3

The remove() method is deprecated.

So using 'remove' in your Mongoose middleware is probably not best practice anymore.

Mongoose has created updates to provide hooks for deleteMany() and deleteOne(). You can those instead.

Person.pre('deleteMany', function(next) {
    var person = this;
    person.model('Assignment').deleteOne({ person: person._id }, next);
});

Solution 4

In case if anyone looking for the pre hook but for deleteOne and deleteMany functions this is a solution that works for me:

const mongoose = require('mongoose');
... 

const PersonSchema = new mongoose.Schema({
  name: {type: String},
  assignments: [{type: mongoose.Schema.Types.ObjectId, ref: 'Assignment'}]
});

mongoose.model('Person', PersonSchema);

.... 

const AssignmentSchema = new mongoose.Schema({
  name: {type: String},
  person: {type: mongoose.Schema.Types.ObjectId, ref: 'Person'}
});

mongoose.model('Assignment', AssignmentSchema)
...

PersonSchema.pre('deleteOne', function (next) {
  const personId = this.getQuery()["_id"];
  mongoose.model("Assignment").deleteMany({'person': personId}, function (err, result) {
    if (err) {
      console.log(`[error] ${err}`);
      next(err);
    } else {
      console.log('success');
      next();
    }
  });
});

Invoking deleteOne function somewhere in service:

try {
  const deleted = await Person.deleteOne({_id: id});
} catch(e) {
  console.error(`[error] ${e}`);
  throw Error('Error occurred while deleting Person');
}

Solution 5

You can leave the document as is, even when the referenced person document is deleted. Mongodb clears references which point to non-existing documents, this doesn't happen immediately after deleting the referenced document. Instead, when you perform action on the document, e.g., update. Moreover, even if you query the database before the references are cleared, the return is empty, instead of null value.

Second option is to use $unset operator as shown below.

{ $unset: { person: "<person id>"} }

Note the use of person id to represent the value of the reference in the query.

Solution 6

you can use soft delete. Do not delete person from Person Collection instead use isDelete boolean flag to true.

Solution 7

you can simply call the model that needs to be deleted and delete that document like this:

PS: This answer is not specific to the question schema.

const Profiles = require('./profile');

userModal.pre('deleteOne', function (next) {
  const userId = this.getQuery()['_id'];
  try {
    Profiles.deleteOne({ user: userId }, next);
  } catch (err) {
    next(err);
  }
});

// in user delete route

exports.deleteParticularUser = async (req, res, next) => {
  try {
    await User.deleteOne({
      _id: req.params.id,
    });

    return res.status(200).json('user deleted');
  } catch (error) {
    console.log(`error`, error);
    return next(error);
  }
};