Reputation: 8972
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
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
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
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