Mongoose findByIdAndUpdate not running validations on subdocuments
Asked Answered
F

3

6

Here's my model:

'use strict';
var nested = new Schema({
    name: String
});

var test = new Schema({
    url   : {
        type      : String,
        // First validation
        validate  : [validator.isURL, 'Invalid URL']
    },
    array : [
        {
            type: nested,

            // Second Validation
            validate: [function(value) {console.log("CALLED"); return value.length <=3;}, 'Too long']
        }
    ]
});

module.exports = Parent.discriminator('Test', test);;

Here's how I create a new document:

Test.findOrCreate({url: url}, function(err, model, created) {
    if (created) {
        model.array = whatever;
    }

    model.save(function(err) {
        if (err) {return res.status(422).json(err);}

        next();
    });
});

And here's the update:

Test.findByIdAndUpdate(id, {$set: {array: whatever}}, {new: true, runValidators: true}, function(err, model) {
    if (err) {
        return res.status(422).json(err);
    }

    res.status(200).json(model);
});

Assume whatever contains an array with length 4 on both cases (new and update).

When creating a new model, both validations work as expected and I get an error. However, when using findByIdAndUpdate, only the first validation is run and the one on the array attribute is ignored (it's not even called). What gives?

Fai answered 3/8, 2015 at 18:45 Comment(2)
Isn't this explained in the documentation? findByIdAndUpdate ignores validations and hooks? (defaults, setters, validators, and middleware all ignored) Kinda surprised it even ran the first validator.Indochina
@KevinB It does. The runValidators options is set to false by default, but I've set it to true on my code, which probably allows to run the first one, but not the second (the one on the subdocuments). It runs pretty much all validators on my model except the one on the subdocument's array. Is there a better way to do this? I need to update my documents through a PUT (actually a PATCH), and the only other alternative I can see is using find and then save, which makes the purpose of findByIdAndUpdate quite confusing to me.Fai
D
10

According to the documentation, validators seems to work only for update() and findOneAndUpdate() if runValidators is set to true.

Have you tried:

Test.findOneAndUpdate({_id: id}, {$set: {array: whatever}}, {new: true, runValidators: true}, function(err, model) {
    if (err) {
        return res.status(422).json(err);
    }

    res.status(200).json(model);
});
Dulcinea answered 25/9, 2015 at 23:44 Comment(0)
E
3

Validators do not work on findByIdAndUpdate (not sure, but didn't work in my case), you can use a middleware to get rid of validators but you need to write some extra code.

Middleware will look like this

[SCHEMA].pre('findOneAndUpdate', function(next){
    // findOneAndUpdate and findByIdAndUpdate, will fire this hook

    console.log(this.getUpdate()); // it will return the update Object
    console.log(this.getQuery());  // it will return the query object
    // Do all validation here and modify the update object
    // this.getUpdate().url = 'stackoverflow.com';

    // for sub-docs you need to use loop;
    if(this.getUpdate() && Array.isArray(this.getUpdate().array))
        this.getUpdate().array = this.getUpdate().array.map(function(val){
            return (val.length <= 3) ? true : 'To Long';
        }); 

    // after all validation call next
    next();
});
Emeldaemelen answered 1/8, 2017 at 11:44 Comment(1)
if you are using $set then this.getUpdate() will give an object like this {$set: {array: []}}Emeldaemelen
J
0

As of 2024 you can still use findByIdAndUpdate, but you have to add the runValidators option as true.

Simple example:

const user = User.findByIdAndUpdate(user._id, {
  $set: {
    "name": "something"
  }
}, {runValidators: true}) // add this
Jaynejaynell answered 17/6 at 7:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.