Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mongoose document schema and validation

I Have a schema like so:

class Schemas

  constructor: ->
    @mongoose = require 'mongoose'
    @schema = @mongoose.Schema

    @EmployeeSchema = new @schema
      'firstname': { type: String, required: true }, 
      'lastname': { type: String, required: true }, 
      'email': { type: String, required: true, index: { unique: true }, validate: /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
      'departmentId': { type: @schema.ObjectId, required: true }
      'enddate': String,
      'active': { type: Boolean, default: true } 

    @EmployeeSchemaModel = @mongoose.model 'employees', @EmployeeSchema

    @DepartmentSchema = new @schema
      'name': { type: String, required: true, index: { unique: true } }
      'employees' : [ @EmployeeSchema ]

    @DepartmentSchemaModel = @mongoose.model 'departments', @DepartmentSchema

So that my employees live in an array of employee documents inside a department

I have several department documents that have a number of employee documents stored in the employees array.

I then added a new department but it contained no employees. If I then attempt to add another department without employees, Mongoose produces a Duplicate key error for the employee.email field which is a required field. The employee.email field is required and unique, and it needs to be.

Is there anyway round this?

like image 944
Simon Lomax Avatar asked Sep 19 '12 15:09

Simon Lomax


People also ask

What is schema validation in Mongoose?

Validation is defined in the Schema. Validation occurs when a document attempts to be saved, after defaults have been applied. Mongoose doesn't care about complex error message construction. Errors have type identifiers.

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.

Does MongoDB support schema validation?

MongoDB uses a flexible schema model, which means that documents in a collection do not need to have the same fields or data types by default. Once you've established an application schema, you can use schema validation to ensure there are no unintended schema changes or improper data types.

Do you need a schema for Mongoose?

With Mongoose, you would define a Schema object in your application code that maps to a collection in your MongoDB database. The Schema object defines the structure of the documents in your collection. Then, you need to create a Model object out of the schema. The model is used to interact with the collection.


2 Answers

If you enable Mongoose debug logging with the coffeescript equivalent of mongoose.set('debug', true); you can see what's going on:

DEBUG: Mongoose: employees.ensureIndex({ email: 1 }) { safe: true, background: true, unique: true }      
DEBUG: Mongoose: departments.ensureIndex({ name: 1 }) { safe: true, background: true, unique: true }      
DEBUG: Mongoose: departments.ensureIndex({ 'employees.email': 1 }) { safe: true, background: true, unique: true }  

By embedding the full EmployeeSchema in the employees array of DepartmentSchema (rather than just an ObjectId reference to it), you end up creating unique indexes on both employees.email and department.employees.email.

So when you create a new department without any employees you are 'using up' the undefined email case in the department.employees.email index as far a uniqueness. So when you try and do that a second time that unique value is already taken and you get the Duplicate key error.

The best fix for this is probably to change DepartmentSchema.employees to an array of ObjectId references to employees instead of full objects. Then the index stays in the employees collection where it belongs and you're not duplicating data and creating opportunities for inconsistencies.

like image 197
JohnnyHK Avatar answered Oct 13 '22 20:10

JohnnyHK


Check out these references:

http://docs.mongodb.org/manual/core/indexes/#sparse-indexes

mongoDB/mongoose: unique if not null (specifically JohnnyHK's answer)

The short of it is that since Mongo 1.8, you can define what is called a sparse index, which only kicks in the unique check if the value is not null.

In your case, you would want:

@EmployeeSchema = new @schema
  'firstname': { type: String, required: true }, 
  'lastname': { type: String, required: true }, 
  'email': { type: String, required: true, index: { unique: true, sparse: true }, validate: /\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/ },
  'departmentId': { type: @schema.ObjectId, required: true }
  'enddate': String,
  'active': { type: Boolean, default: true }

Notice the sparse: true added to your index on EmployeeSchema's email attribute.

https://gist.github.com/juanpaco/5124144

like image 33
juanpaco Avatar answered Oct 13 '22 20:10

juanpaco