9

I'm creating the schema for a mongo document and I can do everything except prevent duplicates in a non-object array.

I'm aware of the addToSet, but I'm referring to Mongo Schema.

I don't want to check on Update using $addToSet, rather I want this to be part of my schema validation.

Example below.

let sampleSchema = {
   name: { type: 'String', unique: true },
   tags: [{ type: 'String', unique: true }]
}

The above snippet prevents name from having duplicate values. It allows tags to be stored as a string array.

But.. I cannot limit the array to be unique strings.

{ name: 'fail scenario', tags: ['bad', 'bad', 'array']}

I'm able to insert this record which should be a fail scenario.

Proximo
  • 6,235
  • 11
  • 49
  • 67
  • Possible duplicate of [Mongoose Unique values in nested array of objects](http://stackoverflow.com/questions/15921700/mongoose-unique-values-in-nested-array-of-objects) – Karlen Jan 22 '17 at 08:44
  • I'm aware of the $addToSet, but I'm referring to Mongo Schema. – Proximo Jan 22 '17 at 13:13

5 Answers5

8
const express = require('express');
const router = express.Router();
const mongoose = require('mongoose');
const _ = require('underscore');

let sampleSchema = new mongoose.Schema({
  name: {
    type: 'String',
    unique: true
  },
  tags: [{
    type: 'String'
  }]
})
sampleSchema.pre('save', function (next) {
  this.tags = _.uniq(this.tags);
  next();
});
const Sample = mongoose.model('sample', sampleSchema, 'samples');


router.post('/sample', function (req, res, next) {
  const sample = new Sample(req.body);

  sample.save()
    .then((sample) => {
      return res.send(sample);
    })
    .catch(err => {
      return res.status(500).send(err.message);
    })
});
Med Lazhari
  • 608
  • 5
  • 9
  • Thanks for the answer but I'm trying to do this with only Schema, not code and not with flags on save, update methods of mongoose. – Proximo Jan 22 '17 at 14:10
  • In underscore, it's _.uniq not _.unique. Also, here is a revised version that works with references: `this.tags = _.uniq(this.tags, function(i) {return (i._id) ? i._id.toString() : i;});` – BuffMcBigHuge Aug 08 '17 at 17:10
4

I've come to the conclusion that this is impossible to do via Mongoose Schema.

JSON schema is done like so.

let schema = {
   name: { type: 'string' }
   tags: { 
      type: 'array',
      items: { type: 'string', uniqueItems: true }
   }
}

I'll validate with JSON schema before creating Mongo Document.

Proximo
  • 6,235
  • 11
  • 49
  • 67
2

This method builds on Med's answer, handles references, and done completely in scheme validation.

let sampleSchema = new mongoose.Schema({
    strings: [{type: 'String'}],
    references: [{type: mongoose.Schema.Types.ObjectId, ref: 'Reference'],
});

sampleSchema.pre('save', function (next) {

    let sample = this;

    sample.strings = _.uniq(sample.strings, function(i) {return (i._id) ? i._id.toString() : i;});
    sample.references = _.uniq(sample.references, function(i) {return (i._id) ? i._id.toString() : i;});

    return next();

});
BuffMcBigHuge
  • 579
  • 5
  • 8
2

I'm a little late, but maybe this will help someone in the future.

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
  },
  reference: {
    type: [mongoose.Schema.Types.ObjectId],
    ref: 'SomeOtherSchema',
    // Add a custom validator.
    validate: {
      // The actual validator function goes here.
      // "arr" will be the value that's being validated (so an array of 
      // mongoose new ObjectId statements, in this case).
      validator: arr => {
        // Convert all of the items in the array "arr", to their string
        // representations.
        // Then, use those strings to create a Set (which only stores unique
        // values).
        const s = new Set(arr.map(String));
        // Compare the Set and Array's sizes, to see if there were any
        // duplicates. If they're not equal, there was a duplicate, and
        // validation will fail.
        return s.size === arr.length;
      },
      // Provide a more meaningful error message.
      message: p => `The values provided for '${ p.path }', ` +
                    `[${ p.value }], contains duplicates.`,
    }
  },
});

The above commented code should be pretty self explanatory.

Jack_Hu
  • 857
  • 6
  • 17
1

With the newer version(s) of MongoDB, you can use $addToSet to append to an array if and only if the new value is unique compared to the items of the array.

Here's the reference: https://www.mongodb.com/docs/manual/reference/operator/update/addToSet/

Here's an example:

const SampleSchema = new mongoose.Schema({
    tags: [String]
});

const Sample = mongoose.model('Sample', SampleSchema);

// append to array only if value is unique
Sample.findByIdAndUpdate({_id: 1, {$addToSet: {tags: "New Tag"}}});

This will effectively update the tags if the "New Tag" is not already present in the tags array. Otherwise, no operation is done.

Amin D.
  • 48
  • 7