6

According to mongoose built-in validators documentations, I can use conditional required field:

const schema = mongoose.Schema({
    a: {
        type: String,
        required: function () {
            return this.b === 1
        }
    },
    b: {
        type: Number,
        required: true
    }
});

In this schema the property a is required only when property b equals to 1.

Trying to create a new document works as expected:

Model.create({ b: 1 }); // throws ValidationError (property a is required)

and

Model.create({ b: 2 }); // creates document

My problem is trying to update an existing document and set property b to 1 so property a should be required.

Running the following code:

Model.findByIdAndUpdate(model._id, { b: 1 }, { new: true, runValidators: true});

unexpectedly updates the document without throwing an error that property a is required.

My guess is that the validation is running only for the updated properties (property b) and not the whole document.

I am not sure if this is the expected behavior or a bug...

Am I missing something? Is there any way to run the validators for the whole document and not only the updated properties without having to manually fetch the document before?

Ron537
  • 990
  • 1
  • 9
  • 20

2 Answers2

0

you have to implement the middle ware normally called "hooks" you can read more on this and implement your needs https://mongoosejs.com/docs/middleware.html

Abdullah Khan
  • 649
  • 5
  • 11
  • This is not correct, with the hooks I have no access to property `a` if I update property `b` only, so there is still no way to validate the field without fetching the document before. – Ron537 Oct 11 '18 at 05:32
0

After playing around with middlewares and validators without any success, I could achieve this requirement by using transactions (Available from MongoDB 4.0 and Mongoose 5.2.0)

// Create a transaction session
const session = await mongoose.startSession();
session.startTransaction();

// Fetch the model (pass the session)
const model = await Model.findById(modelId).session(session);

// ... update your model here

// Validate the schema
if (model.b === 1 && !model.a) {
    throw new mongoose.ValidationError('property a is required');
}

// Save the changes
await model.save();
await session.commitTransaction();

Note that I don't attach the session to the save function because it is already attached from fetching the model using find:

If you get a Mongoose document from findOne() or find() using a session, the document will keep a reference to the session and use that session for save().

One problem I found with this approach is that MongoDB currently only supports transactions on replica sets. There is an option to run it locally without replica sets for development using run-rs

To run a local replica set for development on macOS, Linux or Windows, use npm to install run-rs globally and run run-rs --version 4.0.0. Run-rs will download MongoDB 4.0.0 for you.

For further information you can check both Mongoose documentations for transactions and MongoDB documentations

Ron537
  • 990
  • 1
  • 9
  • 20