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?
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.
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With