Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose - sub document validation not working

I have a Schema that looks like so:

var minderSchema = mongoose.Schema({
  phones: {type: [{
    details: {
      type: {
        country_code: { type: String, required: true },
        region_code: { type: String, required: true },
        number: { type: Number, required: true }
      },
      required: true
    },
  }], required: true},
})

That is... a minder is made up of an array of phones (which are required). Each phone must have a country code, region code and number. Yet the validation does not seem to work. I.e. I can create:

var minder = new Minder({
  "phones": [{
     details: {
        number: "3343434"
     }
   }]
});

Which should not work, because it's missing a country code and region code. In fact I can create a document like this:

var minder = new Minder({
  "phones": [{
    details: {
      "sdf":"sdf"
    }
  }]
});

And it validates.

What concept am I missing?

like image 912
Dominic Bou-Samra Avatar asked May 22 '14 03:05

Dominic Bou-Samra


People also ask

Does mongoose have built-in validation?

Mongoose has several built-in validators. All SchemaTypes have the built-in required validator. The required validator uses the SchemaType's checkRequired() function to determine if the value satisfies the required validator. Numbers have min and max validators.

What is validateBeforeSave?

validateBeforeSave , as the name implies, validates the mongoose object before persisting it to database. This is a schema level check, which, if not set to false, will validate every document. It includes both built-in (like a Number cannot contain string or a required field should exist etc.)

Is Mongoose validation customizable?

Validation is an important part of the mongoose schema. Along with built-in validators, mongoose also provides the option of creating custom validations. Creating custom validations is also very simple. Custom validations can be used where built-in validators do not fulfill the requirements.


1 Answers

The problem here is mostly in how you have constructed your "details" entry here. So despite what you think you may have done, the type of entry that is actually here is a plain sub-document or what would generally be referred to as a "hash/map or dict" depending on which term you are most familiar with.

These are not strictly speaking "typed" in any way, so there is no real control over the "keys" you are placing in there. So what you probably want is something that can actually be structured in a strictly typed way, for example:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

mongoose.connect('mongodb://localhost/test');

var phonesSchema = new Schema({
  country_code: { type: String, required: true },
  region_code: { type: String, required: true },
  number: { type: String, required: true }
});

var minderSchema = new Schema({

  phones:[phonesSchema]

});


var Minder = mongoose.model( 'Minder', minderSchema );

var minder = new Minder({
  "phones": [{ "number": "12345", "bill": "45678" }]
});

console.log( JSON.stringify( minder, undefined, 2 ) );


minder.save();

This not only separates the schema definitions ( which is handy and clean ) but now you have a clearly defined "sub-type" as it were, where the validation can be performed on the entries. You can expand on this if needed, but I generally find this the cleaner form.

A final and important point here is realizing where the "validation" actually happens. So from you example you are just creating instances, but this is not where validation happens. The only place this actually occurs is when the object instance is "saved" and persisted to storage. This allows you to "build" the objects, but is not a strict "validator" of the objects in a traditional "class" sense.

So running the snippet above, you get this output:

{ _id: 537d7c71d4d04b65174d0c00,
  phones: [ { number: '12345', _id: 537d7c71d4d04b65174d0c01 } ] }

events.js:72
        throw er; // Unhandled 'error' event
          ^
No listeners detected, throwing. Consider adding an error listener to your connection.
ValidationError: Path `region_code` is required., Path `country_code` is required.
    at model.Document.invalidate (/home/neillunn/node_modules/mongoose/lib/document.js:1009:32)
    at EmbeddedDocument.invalidate (/home/neillunn/node_modules/mongoose/lib/types/embedded.js:178:19)
    at /home/neillunn/node_modules/mongoose/lib/document.js:958:16
    at validate (/home/neillunn/node_modules/mongoose/lib/schematype.js:610:7)
    at /home/neillunn/node_modules/mongoose/lib/schematype.js:627:9
    at Array.forEach (native)
    at SchemaString.SchemaType.doValidate     (/home/neillunn/node_modules/mongoose/lib/schematype.js:614:19)
    at /home/neillunn/node_modules/mongoose/lib/document.js:956:9
    at process._tickCallback (node.js:419:13)
    at Function.Module.runMain (module.js:499:11)

Noting that the "log" output there kept the "valid" entry but discarded the field that was not defined, and then the "validation" really only occurs for the required fields only when the object actually attempts to "save".

So consider your structure and also what is actually happening with validation in place. Trying to add a field that is not defined does not error, it just discards. Omitting a "required" field, is only checked when the object is persisted which allows you time to build it. These are not required "class constructor" type arguments, but for a different purpose.


If you really want nesting, drop the "type" declaration as in:

var phonesSchema = new Schema({
  details: {
    country_code: { type: String, required: true },
    region_code: { type: String, required: true },
    number: { type: String, required: true }
  }
});

And the validation will work for you:

{
  "_id": "537d9e6d5b433f8745547f52",
  "phones": [
    {
      "_id": "537d9e6d5b433f8745547f53",
      "details": {
        "number": "12345"
      }
    }
  ]
}

events.js:72
    throw er; // Unhandled 'error' event
          ^
No listeners detected, throwing. Consider adding an error listener to your connection.
ValidationError: Path `details.region_code` is required., Path `details.country_code` is required.
like image 107
Neil Lunn Avatar answered Oct 05 '22 12:10

Neil Lunn