Mongoose Unique values in nested array of objects - node.js

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.

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.

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" ] } }
)

Related

MongoDB update query in subarray

An update in the array of objects inside another array of objects.
mongodb field that I'm working on:
otherFields: values,
tasks: [
{
_id: mongodb.objectID(),
title: string,
items:[{
_id: mongodb.objectID(),
title: string,
completed: boolean //field need to be update.
}]
},
{}...
],
otherFields: value
sample mongodb document
I need to find the document using the task_id and the item_id and update a completed field in item of a task. Using the mongoose findOneAndUpdate method
const path = "tasks.$.items." + item_id + "completed";
collectionName.findOneAndUpdate(
{ _id: req.user._id, "tasks._id": taskID },
{ $set: { [path]: true }});
The above query doesn't work!!!
There is no need to use multiple query conditions, since you'd like to update a specific item that has an unique ID. Therefore you could use something along the lines:
collectionName.findOneAndUpdate(
{ 'tasks.items._id': itemID },
...
);
Keep in mind this structure is far away from optimized as it would basically look through the entire database...
Also now that I think of it, you'd also have issue with the update, as there are two nested arrays within the document. Read more here: How to Update Multiple Array Elements in mongodb

Mongoose : How to find documents in which fields match an ObjectId or a string?

This is mongoose DataModel in NodeJs
product: {type: mongoose.Schema.Types.ObjectId, ref: 'products', required: true}
But in DB, this field is having multiple type of values in documents, have String and ObjectId
I'm querying this in mongoose
{
$or: [
{
"product": "55c21eced3f8bf3f54a760cf"
}
,
{
"product": mongoose.Types.ObjectId("55c21eced3f8bf3f54a760cf")
}
]
}
But this is only fetching the documents which have that field stored as ObjectId.
Is there any way that it can fetch all the documents having both type of values either String OR ObjectId?
Help is much appreciated. Thanks
There is a schema in Mongoose, so when you query a document, it will search it by this schema type. If you change the model's product type to "string", it will fetch only documents with string IDs.
Even if there is a way to fetch either a string OR ObjectId, it's smelly to me to have such inconsistency.
I've encountered the same problem, so the solution was to standardize all documents by running a script to update them.
db.products.find().forEach(function(product) {
db.products.update({ type: product.type},{
$set:{ type: ObjectId(data.type)}
});
});
The only problem I see there is if this type field is actually an _id field. _id fields in MongoDB are immutable and they can't be updated. If that is your case, you can simply create a new document with the same (but parsed) id and remove the old one.
This is what I did: (in Robomongo)
db.getCollection('products').find().forEach(
function(doc){
var newDoc = doc;
newDoc._id = ObjectId(doc._id);
db.getCollection('products').insert(newDoc);
}
)
Then delete all documents, which id is a string:
db.getCollection('products').find().forEach(
function(doc){
db.getCollection('products').remove({_id: {$regex: ""}})
}
)
There is another way to do this. If we Update the type to Mixed then it will fetch all the documents with each type, either String or ObjectId
Define this in your Schema
mongoose.Schema.Types.Mixed
Like
product: {type: mongoose.Schema.Types.Mixed, required: true}

Defining a map with ObjectId key and array of strings as value in mongoose schema

I'm facing a problem while creating Mongoose schema for my DB. I want to create a map with a objectId as key and an array of string values as its value. The closest that I can get is:
var schema = new Schema({
map: [{myId: {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'}, values: [String]}]
});
But somehow this is not working for me. When I perform an update with {upsert: true}, it is not correctly populating the key: value in the map. In fact, I'm not even sure if I have declared the schema correctly.
Can anyone tell me if the schema is correct ? Also, How can I perform an update with {upsert: true} for this schema?
Also, if above is not correct and can;t be achieved then how can I model my requirement by some other way. My use case is I want to keep a list of values for a given objectId. I don't want any duplicates entries with same key, that's why picked map.
Please suggest if the approach is correct or should this be modelled some other way?
Update:
Based on the answer by #phillee and this, I'm just wondering can we modify the schema mentioned in the accepted answer of the mentioned thread like this:
{
"_id" : ObjectId("4f9519d6684c8b1c9e72e367"),
... // other fields
"myId" : {
"4f9519d6684c8b1c9e73e367" : ["a","b","c"],
"4f9519d6684c8b1c9e73e369" : ["a","b"]
}
}
Schema will be something like:
var schema = new Schema({
myId: {String: [String]}
});
If yes, how can I change my { upsert:true } condition accordingly ? Also, complexity wise will it be more simpler/complex compared to the original schema mentioned in the thread?
I'd suggest changing the schema so you have one entry per myId,
var schema = new Schema({
myId : {type:mongoose.Schema.Types.ObjectId, ref: 'MyOtherCollection'},
values : [String]
})
If you want to update/upsert values,
Model.update({ myId : myId }, { $set : { values : newValues } }, { upsert : true })

DivergentArrayError in mongoose while updating array of referenced docs

I have a schema defined in mongoose as follow:
var VolunteerSchema = new Schema ({
......
other fields
.....
preferLocations:[{
type: Schema.ObjectId,
ref: 'Location'
}]
.....
});
I am using volunteer.save() method to update the model.
While updating to volunteer model i get the error as follow:
{ [DivergentArrayError: For your own good, using `document.save()` to update an
array which was selected using an $elemMatch projection OR populated using skip,
limit, query conditions, or exclusion of the _id field when the operation resul
ts in a $pop or $set of the entire array is not supported. The following path(s)
would have been modified unsafely:
preferLocations
Use Model.update() to update these arrays instead.]
message: 'For your own good, using `document.save()` to update an array which
was selected using an $elemMatch projection OR populated using skip, limit, quer
y conditions, or exclusion of the _id field when the operation results in a $pop
or $set of the entire array is not supported. The following path(s) would have
been modified unsafely:\n preferLocations\nUse Model.update() to update these a
rrays instead.',
name: 'DivergentArrayError' }
While updating the location I collect the _ids field in array and asigned to preferLocations as given below:
volunteer.preferLocations = locationIdsArray;
I don't get the error when I remove this line.What am I doing wrong?
When using $elemMatch in a projection, do not use document.save(). Instead, manually update your document using Model.update(). In your case you should try
volunteer.findOneAndUpdate(
{
_id: ObjectId("567452bae5b25d6e6c1a0f7e"),
localization: { '$elemMatch': { language: 'de' } }
},
{
$set: { 'localization.$.name' : "Neuer Name" }
}).exec(//...
});
click here more details

How to guarantee unique subdocs by date in Mongo/Mongoose? [duplicate]

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.
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.
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" ] } }
)

Resources