62

For my project, I want to keep a mongoose document for groups of organizations, like this:

var groupSchema = Schema({
  name : { type : String },
  org : { type : Schema.Types.ObjectId, ref : 'Organization' },
  ...
  users : [{
    uid : { type : Schema.Types.ObjectId, ref : 'User' },
    ...
  }]
});

I want to prevent the same user from being in the same group twice. To do this, I need to force users.uid to be unique in the users array. I tried stating 'unique : true' for uid, but that didn't work. Is there a way to do this with mongoose or mongoDB without extra queries or splitting the schema?

Edit: I changed the previous value of uid to uid : { type : Schema.Types.ObjectId, ref : 'User', index: {unique: true, dropDups: true} } But this still doesn't seem to work.

Edit: Assuming there is no simple way to achieve this, I added an extra query checking if the user is already in the group. This seems to me the simplest way.

Guido Passage
  • 1,040
  • 1
  • 10
  • 15
  • check this it will be [helpful](http://stackoverflow.com/questions/12395118/mongodb-setting-unique-field) – karthick Apr 10 '13 at 10:47
  • Thank you for the link. I tried the following: uid : { type : Schema.Types.ObjectId, ref : 'User', index: {unique: true, dropDups: true} } instead of the old value for uid. This still doesn't work, I also tried to restart mongoDB with no success. – Guido Passage Apr 10 '13 at 17:33

2 Answers2

147

A unique index on an array field enforces that the same value cannot appear in the arrays of more than one document in the collection, but doesn't prevent the same value from appearing more than once in a single document's array. So you need to ensure uniqueness as you add elements to the array instead.

Use the $addToSet operator to add a value to an array only if the value is not already present.

Group.updateOne({name: 'admin'}, {$addToSet: {users: userOid}}, ...

However, if the users array contains objects with multiple properties and you want to ensure uniqueness over just one of them (uid in this case), then you need to take another approach:

var user = { uid: userOid, ... };
Group.updateOne(
    {name: 'admin', 'users.uid': {$ne: user.uid}}, 
    {$push: {users: user}},
    function(err, numAffected) { ... });

What that does is qualify the $push update to only occur if user.uid doesn't already exist in the uid field of any of the elements of users. So it mimics $addToSet behavior, but for just uid.

Jamie
  • 3,105
  • 1
  • 25
  • 35
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • But how can you force only uid to be unique? In the users array, I have more attributes which could be different every time I add the same user (like date). – Guido Passage Apr 10 '13 at 17:19
  • Thank you for your input. I've read about validators but I don't see an easier way than adding an extra query checking if the user is already in the group. – Guido Passage Apr 11 '13 at 11:37
  • @GuidoPassage I added a way to support your specific use case. – JohnnyHK Jul 22 '14 at 04:04
  • 6
    How can we ensure this at creation of object first time, Using validation or pre save hooks ? – Anubhav Singh Mar 18 '15 at 07:32
  • How about updating the user with a specific uid? How would that be achieved? – CoderX Mar 14 '18 at 15:05
  • 1
    How would this approach work if there is more then 1 user being added? So an array of objects? Thanks – Miguel Stevens Oct 25 '18 at 07:29
  • Best to ask any followup questions by posting a new question. – JohnnyHK Oct 25 '18 at 15:32
  • https://stackoverflow.com/questions/53016319/push-or-addtoset-for-multiple-items-in-subdocument – Miguel Stevens Oct 26 '18 at 21:08
  • Wouldnt that however scan all Documents of the Model? What if I want to find a Document by Id and then, afterwards check if the value already exists and if it does throw an error. Sounds simple but its so hard to find documentation for this – Tobi Oct 07 '20 at 14:15
  • Does $addToSet work when the schema type for the field is an array? – Kay Nov 26 '20 at 10:31
  • 1
    This doesn't raise an error though while trying to add a user with duplicate id. – Ranjan Jan 22 '21 at 06:02
15

Well this might be old question but for mongoose > v4.1, you can use $addToSet operator.

The $addToSet operator adds a value to an array unless the value is already present, in which case $addToSet does nothing to that array.

example:

MyModal.update(
   { _id: 1 },
   { $addToSet: {letters: [ "c", "d" ] } }
)
Ankit Balyan
  • 1,319
  • 19
  • 31
  • 2
    I don't think this is a good example, as this would append the whole array of `["c", "d"]` to the array `letters` as one element, so letters will be `["a", "b", ["c", "d"]]` see [mongodb docs](https://docs.mongodb.com/manual/reference/operator/update/addToSet/) – Amr Saber Oct 02 '19 at 13:27
  • 3
    $each operator can be used in addition to $addToSet to add all the elements in an array to the array. { $addToSet: { letters: { $each: [ "c", "d"] } } } will do the required work. Thanks – SDK4551 Dec 27 '19 at 07:29
  • Perfect for my need! – Code Cooker Aug 25 '21 at 17:52