Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose custom validation of several fields on update

First of all, this doesn't help.

Let's say, we have a User model:

const schema = new mongoose.Schema({
    active: { type: Boolean },
    avatar: { type: String }
});

const User = mongoose.model('User', schema);

When we update it (set an avatar):

// This should pass validation
User.update({ _id: id }, { $set: { avatar: 'user1.png' } });

We want to validate it based on current (or changed) active attribute value.

Case #1

  • active is false
  • we should not be able to set avatar - it should not pass the validation

Case #2

  • active is true
  • we should be able to set avatar - it should pass the validation

Ideas

  1. Use a custom validator
const schema = new mongoose.Schema({
    active: { type: Boolean },
    avatar: { type: String, validate: [validateAvatar, 'User is not active'] }
});

function validateAvatar (value) {
    console.log(value); // user.avatar
    console.log(this.active); // undefined
}

So this will not work as we don't have an access to active field.

  1. Use pre "validate" hook
schema.pre('validate', function (next) {
    // this will never be called
});

This hook doesn't work with update method.

  1. Use pre "update" hook
schema.pre('update', function (next) {
    console.log(this.active); // undefined
});

This will not work for us as it doesn't have an access to model fields.

  1. Use post "update" hook
schema.post('update', function (next) {
    console.log(this.active); // false
});

This one works, but in terms of validation is not quite good choice, as the function is being called only when model was already saved.

Question

So is there a way to validate the model based on several fields (both saved in DB and new ones) before saving it, while using model.update() method?

As a summary:

  1. Initial user object
{ active: false, avatar: null }
  1. Update
User.update({ _id: id }, { $set: { avatar: 'user1.png' } });
  1. Validation should have an access to
{ active: false, avatar: 'user1.png' }
  1. If validation fails, changes should not be passed to DB
like image 236
Nazar Avatar asked Aug 26 '16 11:08

Nazar


People also ask

Is Mongoose validation customizable?

Validation is asynchronously recursive; when you call Model#save, sub-document validation is executed as well. If an error occurs, your Model#save callback receives it. Validation is customizable.

What methods should you implement for your custom validator?

A Validator implementation must contain a constructor, a set of accessor methods for any attributes on the tag, and a validate method, which overrides the validate method of the Validator interface.

What does Mongoose unique validator do?

mongoose-unique-validator is a plugin which adds pre-save validation for unique fields within a Mongoose schema. This makes error handling much easier, since you will get a Mongoose validation error when you attempt to violate a unique constraint, rather than an E11000 error from MongoDB.

What is the need of Mongoose .how it is useful for validation?

Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node. js. It manages relationships between data, provides schema validation, and is used to translate between objects in code and the representation of those objects in MongoDB. MongoDB is a schema-less NoSQL document database.


1 Answers

Due to limitation of working with update() I've decided to solve the problem this way:

  • Use custom validators (idea #1 mentioned in the question)
  • Don't use update()

So instead of

User.update({ _id: id }, { $set: { avatar: 'user1.png' } });

I use

User.findOne({ _id: id })
    .then((user) => {
        user.avatar = 'user1.png';
        user.save();
    });

In this case custom validators work as expected.

P.S. I choose this answer as a correct one for me, but I will give bounty to the most relevant answer.

like image 163
Nazar Avatar answered Oct 12 '22 23:10

Nazar