Mongoose: findByIdAndUpdate() How To Conditionally Update Based On Existing Data? - mongodb

When using findByIdAndUpdate() or findOneAndUpdate() (that is, updating the database atomically to avoid race conditions), is there a way to reference the existing data to conditionally update a field?
PersonModel.findByIdAndUpdate(
req.user.id,
{
$set: {
// set this to true
// if person.color === blue for example
hasFavoriteColor: true,
}
},
{ new: true },
(err, updatedDoc => {
if (err) return;
return updatedDoc;
});
);

I'm not sure you can do that, I see nowhere in the document. However, for your query particularly, you can add the condition about the color in the filter part, something like this :
PersonModel.findOneAndUpdate(
{_id : req.user.id, color : "blue"},
{
$set: {
// set this to true
// if person.color === blue for example
hasFavoriteColor: true,
}
},
{ new: true },
(err, updatedDoc => {
if (err) return;
return updatedDoc;
});
);

Related

Can't add array to mongodb

I'm trying to send an array to mongodb, but the res.json(user) returns an empty biddingGroup:[] and mongodb document never has field biddingGroup appear. I've looked at stack posts and have seen suggestions for schema.
I've tried
biddingGroup: [{type: String}],
biddingGroup: [String],
biddingGroup: {type: String},
I haven't found a working schema that captures the data yet.
I even hardcoded biddingGroup: ['test'] too, but it never shows up.
app.js
app.put('/api/listings/:id', (req, res) =>
Post.update({
id: req.query.id
}, {
$set: {
currentBid: req.query.currentBid,
lastBidTimeStamp: req.params.lastBidTimeStamp,
biddingGroup: ['test']
}
}, {
multi: false //set to false to ensure only one document gets updated
}).exec().then(data => {
console.log(data);
}, err => {
console.log(err);
})
);
Any help is appreciated.
You need to use exec() at the end to run the query. That is the function that actually runs the request and returns you the promise. Plus your usage of the update function in general is off.
Try this:
Post.update({
id: req.query.id
}, {
$set: {
currentBid: req.query.currentBid,
lastBidTimeStamp: req.params.lastBidTimeStamp,
biddingGroup: ['test']
}
}, {
multi: false //set to false to ensure only one document gets updated
}).exec().then(data => {
console.log(data);
}, err => {
console.log(err);
});

Update query adding ObjectIDs to array twice

I am working on a table planner application where guests can be assigned to tables. The table model has the following Schema:
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const tableSchema = new mongoose.Schema({
name: {
type: String,
required: 'Please provide the name of the table',
trim: true,
},
capacity: {
type: Number,
required: 'Please provide the capacity of the table',
},
guests: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Guest',
}],
});
module.exports = mongoose.model('Table', tableSchema);
Guests can be dragged and dropped in the App (using React DND) to "Table" React components. Upon being dropped on a table, an Axios POST request is made to a Node.js method to update the Database and add the guest's Object ID to an array within the Table model:
exports.updateTableGuests = async (req, res) => {
console.log(req.body.guestId);
await Table.findOneAndUpdate(
{ name: req.body.tablename },
{ $push: { guests: req.body.guestId } },
{ safe: true, upsert: true },
(err) => {
if (err) {
console.log(err);
} else {
// do stuff
}
},
);
res.send('back');
};
This is working as expected, except that with each dropped guest, the Table model's guests array is updated with the same guest Object ID twice? Does anyone know why this would be?
I have tried logging the req.body.guestID to ensure that it is a single value and also to check that this function is not being called twice. But neither of those tests brought unexpected results. I therefore suspect something is wrong with my findOneAndUpdate query?
Don't use $push operator here, you need to use $addToSet operator instead...
The $push operator can update the array with same value many times
where as The $addToSet operator adds a value to an array unless the
value is already present.
exports.updateTableGuests = async (req, res) => {
console.log(req.body.guestId);
await Table.findOneAndUpdate(
{ name: req.body.tablename },
{ $addToSet : { guests: req.body.guestId } },
{ safe: true, upsert: true },
(err) => {
if (err) {
console.log(err);
} else {
// do stuff
}
},
);
res.send('back');
};
I am not sure if addToSet is the best solution because the query being executed twice.
If you used a callback and a promise simultaneously, it would make the query executes twice.
So choosing one of them would make it works fine.
Like below:
async updateField({ fieldName, shop_id, item }) {
return Shop.findByIdAndUpdate(
shop_id,
{ $push: { menuItems: item } },
{ upsert: true, new: true }
);
}

Mongoose query using if else possible?

I have this Schema:
const guestSchema = new Schema({
id: String,
cart: [
{
product: {
type: mongoose.Schema.ObjectId,
ref: "products"
},
quantity: Number
}
]
});
I have this query:
Guest.findOneAndUpdate(
{ id: req.sessionID },
{
$cond: [
{ "cart.product": { $ne: req.body.itemID } },
{ $push: { "cart": { product: req.body.itemID, quantity: 1 } } },
{ $inc: { "cart.quantity": 1 } }
]
},
{ upsert: true, new: true }
).exec(function(err, docs) {
err ? console.log(err) : res.send(docs);
});
Basically, what I'm trying to do is update based on a condition. I tried using $cond, but found out that operator isn't used for querys like I'm doing.
Based on this:
{ $cond: [ <boolean-expression>, <true-case>, <false-case> ] }
I want something similar to the functionality of this operator for my query.
Let's break down my condition:
For my boolean expression: I want to check if req.body.itemID is $ne to any of the values in my cart
If true then: $push the itemID and quantity into the cart
Else (then item already exists): $inc the quantity by 1
Question: How would achieve this result? Do I need to make two seperate querys? I'm trying to avoid doing that if possible
I went through all their Update Field Operators, and there's probably no way to do this in the way I want.
I wonder why there is no $cond for update operators. Nonetheless, I have the solution to what I wanted the functionality accomplish. Just not in the elegant fashion that I would like it.
Guest.findOneAndUpdate(
{ id: req.sessionID },
{ id: req.sessionID }, //This is here in case need to upsert new guest
{ upsert: true, new: true }
).exec(function(err, docs) {
if (err) {
console.log(err);
} else {
//Find the index of the item in my cart
//Returns (-1) if not found
const item = doc.cart.findIndex(
item => item.product == req.body.itemID
);
if (item !== -1) {
//Item found, so increment quantity by 1
doc.cart[item].quantity += 1;
} else {
//Item not found, so push into cart array
doc.cart.push({ product: req.body.itemID, quantity: 1 });
}
doc.save();
}
});
This type of logic does not belong within the database query. It should happen in the application layer. MongoDB is also very fast at retrieving and updating single records with an index so that should not be a concern.
Please try doing something like this:
try {
const guest = await Guest.findOne().where({
id: req.sessionID
}).exec();
// your cond logic, and update the object
await guest.save();
res.status(200).json(guest);
} catch (error) {
handleError(res, error.message);
}

Get ID of affected document by using update()

I'm using this for doing an upsert:
Articles.update(
{ title: title },
{
title: title,
parent: type
},
{ upsert: true },
function(res) {
return console.log(res);
}
);
console.log(needToGetID);
Now I need to get the _id of the document which has been updated or inserted. I thought I can get that via callback, but res is undefined.
I assume there is just one unique document which is defined by the query.
Update
Forgot to mention that I'm using meteor...
The intent of .update() is to basically just "update" the matching document(s) ( as "multi" can also be applied here ) and be done with it, therefore that especially considering this "could" be applied to multiple documents then returning such information would not really make sense in the terms of that operation.
However if your intent is to modifiy a single "specific docucment", then the .findOneAndUpdate() method would apply assuming you are using mongoose:
Articles.findOneAndUpdate(
{ title: title },
{
title: title,
parent: type
},
{ upsert: true, new: true },
function(res) {
return console.log(res);
}
);
Also note the new: true which is important, as without it the default behavior is to return the document "before" it was modified by the statement.
At any rate, as the return here is the document that is matched and modified, then the _id and any other value is present in the response.
With meteor you can add a plugin to enable .findAndModify() which is the root method:
meteor add fongandrew:find-and-modify
And then in code:
Articles.findAndModify(
{
"query": { title: title },
"update": {
title: title,
parent: type
},
"upsert": true,
"new": true
},
function(res) {
return console.log(res);
}
);
Note that you should also really be using the $set operator, as without it the changes basically "overwrite" the target document:
Articles.findAndModify(
{
"query": { "title": title },
"update": {
"$set": {
"parent": type
}
},
"upsert": true,
"new": true
},
function(res) {
return console.log(res);
}
);

How can I update multiple documents in mongoose?

I found the following script:
Device.find(function(err, devices) {
devices.forEach(function(device) {
device.cid = '';
device.save();
});
});
MongoDB has the "multi" flag for an update over multiple documents but I wasn't able to get this working with mongoose. Is this not yet supported or am I doing something wrong?
Device.update({}, {cid: ''}, false, true, function (err) {
//...
});
Currently I believe that update() in Mongoose has some problems, see:
https://groups.google.com/forum/#%21topic/mongoose-orm/G8i9S7E8Erg
and https://groups.google.com/d/topic/mongoose-orm/K5pSHT4hJ_A/discussion.
However, check the docs for update: http://mongoosejs.com/docs/api.html (its under Model). The definition is:
Earlier Solution(Depreciated after mongoose 5+ version)
Model.update = function (query, doc, options, callback) { ... }
You need to pass the options inside an object, so your code would be:
Model.update = function ({}, {cid: ''}, {multi: true}, function(err) { ... });
New Solution
Model.updateMany = function (query, doc, callback) { ... }
Model.updateMany = function ({}, {cid: ''}, function(err) { ... });
I believe that Mongoose wraps your cid in a $set, so this is not the same as running that same update in the mongo shell. If you ran that in the shell then all documents would be replaced by one with a single cid: ''.
Those answers are deprecated. This is the actual solution:
Device.updateMany({}, { cid: '' });
You have to use the multi: true option
Device.update({},{cid: ''},{multi: true});
as mentioned in the mongoose documents this is how we do this:
db.collection.updateMany(condition, update, options, callback function)
so this is an example based on the docs:
// creating arguments
let conditions = {};
let update = {
$set : {
title : req.body.title,
description : req.body.description,
markdown : req.body.markdown
}
};
let options = { multi: true, upsert: true };
// update_many :)
YourCollection.updateMany(
conditions, update, options,(err, doc) => {
console.log(req.body);
if(!err) {
res.redirect('/articles');
}
else {
if(err.name == "ValidationError"){
handleValidationError(err , req.body);
res.redirect('/new-post');
}else {
res.redirect('/');
}
}
});
this worked fine for me, I hope it helps :)
await Device.updateMany({_id: {$in: cid}},{ $set: {columnNameHere: "columnValueHere"}},{multi:true,upsert: true,new: true});
as #sina mentioned:
let conditions = {};
let options = { multi: true, upsert: true };
Device.updateMany(conditions , { cid: '' },options );
you can add a callback function after options but it's not necessary.
You can try the following way
try {
const icMessages = await IcMessages.updateMany({
room: req.params.room
}, {
"$set": {
seen_status_2: "0"
}
}, {
"multi": true
});
res.json(icMessages)
} catch (err) {
console.log(err.message)
res.status(500).json({
message: err.message
})
}