Mongoose findOneAndUpdate when value is not in array - node.js

I have the below Mongoose function where I am trying to do a select WHERE email = req.body.email and referenceId is not in an array.
referenceId is a string array [String] which has an array of object ID's.
const refereeSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
referenceId: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}
],
token: {
type: String,
required: true
},
password: {
type: String
},
company: {
type: String,
required: true
},
role: {
type: String,
required: false
},
startedOn: Date,
endedOn: Date,
createdAt: Date,
updatedAt: Date
});
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$ne': [new mongo.ObjectID(req.params.referenceId)]
}
}, {
$push: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
Each time I run the above it always matches and pushes the ObjectId even though it already existed before.
{ referenceId:
[ 5ba94232b492890982e7a417,
5ba94232b492890982e7a417,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f ],
_id: 5c20e86d72d5c1ce20b961fa,
firstName: 'Richard',
lastName: 'Hendricks',
email: 'hello#domain.com',
token: '319d9f5a-68e1-6f47-a9f1-7fbbf617a45c',
company: 'Pied Piper',
role: '',
__v: 0 }
Could someone please help me?
I'd like to do a check to see if the ObjectId is already in the array then DO NOTHING else push it.

$push will just push data in array, you should use $addToSet
$addToSet only ensures that there are no duplicate items added to the set and does not affect existing duplicate elements. $addToSet does not guarantee a particular ordering of elements in the modified set
more details
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$ne': [new mongo.ObjectID(req.params.referenceId)]
}
}, {
$addToSet: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
more ever, you can use $elemMatch or $nin as find query, so that document itself doesnt return if id is in referenceId
$elemMatch :
referenceId: {
'$elemMatch': { '$ne': new mongo.ObjectID(req.params.referenceId) }
}
$nin:
referenceId: {
'$nin': [new mongo.ObjectID(req.params.referenceId)]
}

Consider using the $elemMatch operator in complement of $ne. $ne against the array itself will always return true in this case, because the array you're passing in the parameter is never going to equal the array in the document.
I haven't tested this, but something along these lines should work:
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$elemMatch': { '$ne': new mongo.ObjectID(req.params.referenceId) }
}
}, {
$push: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
Also, you probably already know this but there are actually two ObjectId classes you might be importing, the one you want is mongoose.Types.ObjectId

Related

I have problem updating a subdocument in an array of subdocuments in MongoDB

I have problems updating a subdocument in an array of subdocuments.
Here is my data structure in the users collection:
{
favorites: [
{
id: new ObjectId("639707f36bf9468265d91810"),
expiresAt: 1671361200000,
reminder: false
},
{
id: new ObjectId("637cc4c986b4fbec43579e1f"),
expiresAt: 1672603200000,
reminder: false
}
],
_id: new ObjectId("637e8af40e43f40373686da2"),
email: 'something#something.com',
forename: 'something',
surname: 'something',
role: 'user',
password: 'something',
__v: 0
}
My Schema is:
const userSchema = new Schema({
email: String,
forename: String,
surname: String,
role: String,
password: String,
favorites: {
id: { type: Schema.Types.ObjectId, ref: "Event" },
expiresAt: Number,
reminder: Boolean,
},
});
I want to update the reminder field in a subdocument based on the subdocument’s id.
I’ve tried following approaches:
1.
User.findOneAndUpdate(
{ _id: req.body.user, "favorites.id": { $eq: BSON.ObjectId(req.body.id) } },
{ $set: { "favorites.$.reminder": true } },
).setOptions({ sanitizeFilter: true });
Here nothing happens. It finds the document but does not update it.
2.
User.findOneAndUpdate(
{ _id: req.body.user },
{ $set: { "favorites.$[elem].reminder": true } },
{
arrayFilters: [{ "elem.id": { $eq: BSON.ObjectId(req.body.id) } }],
returnNewDocument: true,
}
).setOptions({ sanitizeFilter: true });
Here it returns an error: “Error: Could not find path “favorites.0.id” in schema”
I cannot find where is my mistake? Any help is much appreciated!
P.S.
Mongo version is 5.0.14
Try to use updateMany instead.
User.updateMany(
{
_id: userId,
"favorites.id": eventId
},
{
$set: {
"favorites.$.reminder": true
}
},
function(err, res) {
if (err) {
// Handle error
} else {
// Handle success
}
}
);
I think you can adapt the query to your calling method findOneAndUpdate. But it's enough to you.

updateMany and elemMatch in with nested schemas in mongoose (Node.js)

I'm trying to query a MongoDB database via mongoose to updateMany the fields of my database. I suppose that the first request is correct because mongoose doesn't fire any error, but for the nested schemas, I'm getting the following error.
My goal is to delete the occurences of the userTag in friends and remove the friendRequestsSent when userTarget equals userTag, friendRequestsReceived when userRequest equals userTag and notification when data equals userTag.
Here are the schemas of my Model
const NotificationSchema = new Schema({
title: String,
type: Number,
icon: String,
data: String,
createdAt: { type: Date, default: Date.now },
})
const FriendRequestSchema = new Schema({
userRequest: { type: String, required: true },
userTarget: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
})
const UserSchema = new Schema({
tag: { type: String, required: true, unique: true },
friendRequestsSent: { type: [FriendRequestSchema] },
friendRequestsReceived: { type: [FriendRequestSchema] },
friends: { type: [String] },
notifications: { type: [NotificationSchema] },
})
The request
const updateResponse = await User.updateMany(
{
friends: { $elemMatch: { $eq: userTag } },
friendRequestsSent: {
userTarget: {
$elemMatch: { $eq: userTag },
},
},
friendRequestsReceived: {
userRequest: {
$elemMatch: { $eq: userTag },
},
},
notifications: {
data: {
$elemMatch: { $eq: userTag },
},
},
},
{
$pull: {
friends: userTag,
friendRequestsSent: { userTarget: userTag },
friendRequestsReceived: { userRequest: userTag },
notifications: { data: userTag },
},
}
)
The error
Error while deleting the user account: Cast to String failed for value "{ '$elemMatch': { '$eq': '0eQzaAwpt' } }" at path "userRequest" for model "User"
The userRequest field in friendRequestsReceived is type String, not array so $elemMatch will not work. Also, you don't need to use $elemMatch because you specify only a single condition in the $elemMatch expression as it says in the docs:
If you specify only a single condition in the $elemMatch expression, you do not need to use $elemMatch.
In your case, you just need to do something like (details here):
await User.updateMany({
friends: userTag,
"friendRequestsSent.userTarget" : userTag,
"friendRequestsReceived.userRequest": userTag,
"notifications.data": userTag
}...

How to do query in mongoose

let UserSchema = new Schema({
created: {
type: Date,
default: Date.now,
required: true
},
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
role: {
default: 'user',
type: String, //we can consider using array of strings as in case user has several roles at the same time
requierd: true
},
devices: [
{
id: '1',
permissions:['start','stop']
},
{
id: '2',
permissions:['start','restart']
}
]
});
How can i push new {id:'2', permissions:['stop']} without duplications, when i receive it in req.params.headers. I need to check is id:'2' already exist or not, then I'm shuld check is permission['stop'] is exist, and then if it isn't exist i should push it in collection.
If you want to find any object that matchs either the id or the permission, you can do it with $or operand, something like this:
return User.findOne({ $or:[ {'devices.id': idValue}, {'devices.permissions': permission} ]}).exec()
.then(user => {
if(user) {
console.log("We don't have to push it");
} else {
console.log("We have to push it");
}
});
You can use update with elemMatch in condition to find the inner object. To avoid adding duplicate keys in array, you can use $addToSet
<>.update({"devices": { "$elemMatch": { "id": "2" }}},{ "$addToSet": { "devices.$.permissions": "stop" }})

Mongoose Schema.update doesn't update boolean

I have tried updating other fields and it works just fine.
The command I am using in my API:
User.update({ email: targetUser.email }, { $set: { isAdmin: true }, $push: { 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
To clarify, when I try to run this, the API returns a code 200, meaning everything worked fine, but when I check the database the isAdmin value wasn't changed.
Any suggestions would be helpful, running out of ideas here!
User Schema as requested:
var UserSchema = new Schema({
name: { type: String, default: "", index: 'text' },
email: { type: String, lowercase: true },
role: { type: String, default: "" },
meta: {
skills: { type: Array, default: [], index: 'text' },
about: { type: String, default: "", index: 'text' },
education: { type: Array, default: [], index: 'text' },
location: {
address: {
a: { type: String, default: "" },
p: { type: String, default: "" },
c: { type: String, default: "" }
},
geo: {
lat: { type: Number, default: 0 },
lng: { type: Number, default: 0 }
}
}
},
compMeta:
{
departments: { type: Array, default: [], index: 'text' },
employees:
[
{
emId: Number,
empName: String,
empDep: String // Dunno if i should use Dep name or Dep ID gonna look in to that later
}
],
}
,
settings: {
search: {
distance: {
n: { type: Number, default: 100 },
t: { type: String, default: "km" }
}
}
},
created: {
type: Date,
default: Date.now
},
//Rating is an array of objects that consist of rateing 0-100 , job database id , comments from the Company
rating:
[
{
rate: Number,
jobId: Number,
jobComments: String
}
],
/*rating:
{
userTotalRating: {type: Number, default: 0},
ratingCounter : {type: Number, default: 0}
}*/
sensitive: {
cpr_cvr: String,
},
stripe: { type: String },
facebook: {},
linkedin: {},
log: {
updated: { type: Array, default: [] }
},
hashedPassword: String,
provider: { type: String, default: 'local' },
salt: String
});
UPDATE:
Mongodb version: 3.0.7
Turns out I just forgot to add the isAdmin field to my User Schema! Also, my call to the update was wrong, I changed it to this:
User.update({ email: targetUser.email }, { $set: { isAdmin: true }}, { $push: { 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
Thanks to everyone that put an effort to help me! :)
I encountered a similar problem. The solution was to add the callback.
This doesn't work:
Ride.updateOne({driver:req.body.id},{$set:{isBusy:true}});
This works:
Ride.updateOne({driver:req.body.id},{$set:{isBusy:true}},(e,s)=>{});
Try updating two fields with $set
User.update({ email: targetUser.email }, { $set: { isAdmin: true, 'log.updated': new Date() } }, function (err, user) {
if (err) {
responseObject.err = err;
responseObject.data = null;
responseObject.code = 422;
return res.json(responseObject);
}
return res.json(responseObject);
});
Hope it's works.
There is easier way to handle the issue. As per the documentation, the second parameter is the object where you can update the statement.
A.findByIdAndUpdate(id, update, options, callback)
So you just need to take everything inside the update object.
User.update({ email: targetUser.email, $set: {isAdmin: true}} // ... etc

Mongodb using addtoSet to ensure there are no duplicates

Using mongoose against mongodb 2.6 - another issue raised that sounds similar to mine;
https://github.com/LearnBoost/mongoose/issues/1677
I have this piece of code:
$addToSet: {
invite_list: {
$each : [
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' }]
}
}
which should only store one item but instead its storing 4!
However, changing the query to this
$addToSet: {
invite_list: {
$each : [
'test#test.com',
'test#test.com',
'test#test.com',
'test#test.com' ]
}
}
returns one item, as expected.
Model schema field:
invite_list: [{email: {type: String, trim: true}}],
The query looks like this;
UserModel.findOneAndUpdate(
{
_id: req.params.id,
},
{
$addToSet: {invite_list: { $each : [{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' }] }}
}, function (err, user) {
// morel logic here...
return res.status(200).json(user);
});
http://docs.mongodb.org/manual/reference/operator/update/addToSet/
Is there something Im missing.
Thanks.
J
After reading this, it got me thinking;
Stop Mongoose from creating _id property for sub-document array items
Found a fix;
Model now looks like this;
var subSchema = new mongoose.Schema({email: {type: String, trim: true, _id: false}},{ _id : false })
invite_list: [subSchema],
Works as expected...
J

Resources