Mongoose plugin for enforcing unique
constraints in arrays
Requires mongoose >= 4.10.0
. Do not use with mongoose 3.x.
const arrayUniquePlugin = require('mongoose-unique-array');
If you set the unique
property to true
on a schema path, this plugin
will add a custom validator that ensures the array values are unique before
saving.
const schema = new mongoose.Schema({
arr: [{ type: String, unique: true }],
docArr: [{ name: { type: String, unique: true } }]
});
// Attach the plugin to the schema
schema.plugin(arrayUniquePlugin);
const M = mongoose.model('Test', schema);
M.create({}, function(error, doc) {
assert.ifError(error);
doc.arr.push('test');
doc.docArr.push({ name: 'test' });
doc.save(function(error) {
assert.ifError(error);
doc.arr.push('test');
doc.save(function(error) {
assert.ok(error);
// MongooseError: Duplicate values in array `arr`: [test,test]
assert.ok(error.errors['arr'].message.indexOf('Duplicate values') !== -1,
error.errors['arr'].message);
});
});
});
In mongoose, unique
is not a validator,
but a shorthand for creating a unique index in MongoDB.
The unique index on arr
in the previous example would prevent multiple documents from
having the value 'test' in arr
, but would not prevent a single document from having
multiple instances of the value 'test' in arr
.
const schema = new mongoose.Schema({
arr: [{ type: String, unique: true }]
});
// Do *not* attach the plugin
// schema.plugin(arrayUniquePlugin);
const M = mongoose.model('Test2', schema);
// Since `unique` creates an index, need to wait for the index to finish
// building before the `unique` constraint kicks in.
M.on('index', function(error) {
assert.ifError(error);
M.create({ arr: ['test'] }, function(error, doc) {
doc.arr.push('test');
doc.save(function(error) {
// No error! That's because, without this plugin, a single doc can have
// duplicate values in `arr`. However, if you tried to `save()`
// a separate document with the value 'test' in `arr`, it will fail.
});
});
});
push()
This plugin attaches a custom validator to handle duplicate array entries.
However, there is an additional edge case to handle: calling push()
on
an array translates into a $push
in MongoDB,
so if you have multiple copies of a document calling push()
at the same
time, the custom validator won't catch it. This plugin will surface a
separate error for this case.
const M = mongoose.model('Test');
// Create a document with an empty `arr`
M.create({}, function(error, doc) {
assert.ifError(error);
// Get 2 copies of the same underlying document
M.findById(doc, function(error, doc1) {
M.findById(doc, function(error, doc2) {
// `push()` and `save()` on the first doc. Now `doc2` is out of date,
// it doesn't know that `doc1` pushed 'test'.
doc1.arr.push('test');
doc1.save(function(error) {
assert.ifError(error);
doc2.arr.push('test');
doc2.save(function(error) {
// Because of plugin, you'll get the below error
// VersionError: No matching document found for id "59192cbac4fd9871f28f4d61"
});
});
});
});
});