Simon Lomax
Simon Lomax

Reputation: 8972

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?

Upvotes: 7

Views: 8318

Answers (3)

juanpaco
juanpaco

Reputation: 6433

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

Upvotes: 1

JohnnyHK
JohnnyHK

Reputation: 311865

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.

Upvotes: 6

Simon Lomax
Simon Lomax

Reputation: 8972

It appears that you can't create a unique index on an individual field of a sub-document. Although the db.collection.ensureIndex function in the Mongo shell appears to let you do that, it tests the sub-document as a whole for its uniqueness and not the individual field.

You can create an index on an individual field of a sub-document, you just can't make it unique.

Upvotes: 0

Related Questions