Skip to content

Commit 05ac578

Browse files
committed
Adding functionality to allow duplicate entries
when validating an array of ID values (GitHub issue #12). Bumped version for release
1 parent 0e4590d commit 05ac578

4 files changed

Lines changed: 90 additions & 18 deletions

File tree

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ Model.plugin(id-validator, {
129129
* Defaults to built-in mongoose connection if not specified.
130130
*/
131131
connection: myConnection
132+
133+
/* Applies to validation of arrays of ID references only. Set
134+
* to true if you sometimes have the same object ID reference
135+
* repeated in an array. If set, the validator will use the
136+
* total of unique ID references instead of total number of array
137+
* entries when checking the database.
138+
*
139+
* Defaults to false
140+
*/
141+
allowDuplicates: true
132142
});
133143
```
134144

lib/id-validator.js

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
var mongoose = require('mongoose');
22
var Schema = mongoose.Schema;
33

4-
function IdValidator(){
4+
function IdValidator() {
55
this.enabled = true;
66
}
77

8-
IdValidator.prototype.enable = function(){
8+
IdValidator.prototype.enable = function () {
99
this.enabled = true;
1010
}
1111

12-
IdValidator.prototype.disable = function(){
12+
IdValidator.prototype.disable = function () {
1313
this.enabled = false;
1414
}
1515

16-
IdValidator.prototype.validate = function(schema, options){
16+
IdValidator.prototype.validate = function (schema, options) {
1717
var self = this;
1818
options = options || {};
1919
var message = options.message || "{PATH} references a non existing ID";
2020
var connection = options.connection || mongoose;
21+
var allowDuplicates = options.allowDuplicates || false;
2122

2223
var caller = (self instanceof IdValidator) ? self : IdValidator.prototype;
2324

24-
return caller.validateSchema(schema, message, connection);
25+
return caller.validateSchema(schema, message, connection, allowDuplicates);
2526
}
2627

27-
IdValidator.prototype.validateSchema = function(schema, message, connection) {
28+
IdValidator.prototype.validateSchema = function (schema, message, connection, allowDuplicates) {
2829
var self = this;
2930
var caller = (self instanceof IdValidator) ? self : IdValidator.prototype;
3031
schema.eachPath(function (path, schemaType) {
@@ -54,15 +55,15 @@ IdValidator.prototype.validateSchema = function(schema, message, connection) {
5455

5556
if (validateFunction) {
5657
schema.path(path).validate(function (value, respond) {
57-
if(!(self instanceof IdValidator) || self.enabled){
58-
return validateFunction(this, connection, refModelName, value, conditions, respond);
58+
if (!(self instanceof IdValidator) || self.enabled) {
59+
return validateFunction(this, connection, refModelName, value, conditions, respond, allowDuplicates);
5960
}
6061
return respond(true);
6162
}, message);
6263
}
6364

6465
});
65-
}
66+
};
6667

6768
function executeQuery(query, conditions, validateValue, respond) {
6869
for (var fieldName in conditions) {
@@ -85,13 +86,22 @@ function validateId(doc, connection, refModelName, value, conditions, respond) {
8586
executeQuery(query, conditions, 1, respond);
8687
}
8788

88-
function validateIdArray(doc, connection, refModelName, values, conditions, respond) {
89+
function validateIdArray(doc, connection, refModelName, values, conditions, respond, allowDuplicates) {
8990
if (values == null || values.length == 0) {
9091
return respond(true);
9192
}
93+
94+
var checkValues = values;
95+
if (allowDuplicates) {
96+
//Extract unique values only
97+
checkValues = values.filter(function (v, i) {
98+
return values.indexOf(v) === i
99+
});
100+
}
101+
92102
var refModel = connection.model(refModelName);
93-
var query = refModel.count().where('_id')['in'](values);
94-
executeQuery(query, conditions, values.length, respond);
103+
var query = refModel.count().where('_id')['in'](checkValues);
104+
executeQuery(query, conditions, checkValues.length, respond);
95105
}
96106

97107
module.exports = IdValidator.prototype.validate;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mongoose-id-validator",
3-
"version": "0.1.10",
3+
"version": "0.2.0",
44
"description": "Mongoose plugin to validate that ObjectID references refer to objects that actually exist in the referenced collection",
55
"author": {
66
"name": "Martin Campbell",
@@ -32,7 +32,7 @@
3232
"test": "./node_modules/.bin/mocha -R spec"
3333
},
3434
"devDependencies": {
35-
"mongoose": "~3.8.0",
35+
"mongoose": "*",
3636
"async": "~0.2.9",
3737
"should": "~3.0.1",
3838
"mocha": "^2.2.1"

test/id-validator.js

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ if (process.env.MONGO_PORT_27017_TCP_PORT) {
1212
}
1313
var connection2;
1414

15-
function validatorConcept(schema){
15+
function validatorConcept(schema) {
1616

1717
var idvalidator = new IdValidator();
1818
schema.plugin(IdValidator.prototype.validate.bind(idvalidator));
1919

20-
schema.statics.enableValidation = function(){
20+
schema.statics.enableValidation = function () {
2121
idvalidator.enable();
2222
}
2323

24-
schema.statics.disableValidation = function(){
24+
schema.statics.disableValidation = function () {
2525
idvalidator.disable();
2626
}
2727
}
@@ -351,7 +351,59 @@ describe('mongoose-id-validator Integration Tests', function () {
351351
err.errors.should.property('femaleFriends');
352352
done();
353353
});
354-
})
354+
});
355+
});
356+
357+
describe('Array Duplicate Tests', function () {
358+
359+
var InventoryItemSchema = new Schema({
360+
name: String
361+
});
362+
363+
function createInventorySchema(options) {
364+
var s = new Schema({
365+
items: [
366+
{
367+
type: Schema.Types.ObjectId,
368+
ref: 'InventoryItem'
369+
}
370+
]
371+
});
372+
s.plugin(validator, options);
373+
return s;
374+
}
375+
376+
var InventoryNoDuplicatesSchema = createInventorySchema();
377+
var InventoryDuplicatesSchema = createInventorySchema({
378+
allowDuplicates: true
379+
});
380+
381+
382+
var InventoryItem = mongoose.model('InventoryItem', InventoryItemSchema);
383+
var InventoryNoDuplicates = mongoose.model('InventoryNoDuplicates', InventoryNoDuplicatesSchema);
384+
var InventoryDuplicates = mongoose.model('InventoryDuplicatesSchema', InventoryDuplicatesSchema);
385+
386+
var item1 = new InventoryItem({name: 'Widgets'});
387+
388+
before(function (done) {
389+
async.series([
390+
item1.save.bind(item1)
391+
], done);
392+
});
393+
394+
it('Should fail to validate duplicate entries with default option', function (done) {
395+
var i = new InventoryNoDuplicates({items: [item1, item1]});
396+
i.validate(function (err) {
397+
err.should.property('name', 'ValidationError');
398+
err.errors.should.property('items');
399+
done();
400+
});
401+
});
402+
403+
it('Should pass validation of duplicate entries when allowDuplicates set', function (done) {
404+
var i = new InventoryDuplicates({items: [item1, item1]});
405+
i.validate(done);
406+
});
355407

356408
});
357409

0 commit comments

Comments
 (0)