Mongoose update with limit - node.js

I am looking to update X documents all at once. The short is I basically need to randomly select N documents and then update them as "selected". I'm trying to design an API that needs to randomly distribute questions. I can not find a way to do this in mongoose I have tried:
update ends up selecting everything
Question
.update({}, {
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, {multi:true})
.limit(4)
--- I also tried
Question
.find()
.sort({
answerCount: 1,
lockedCount: 1
})
.limit(req.query.limit || 4)
.update({}, {
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, { multi: true }, callback);
Both resulted in updating all docs. Is there a way to push this down to mongoose without having to use map ? The other thing I did not mention is .update() without multi resulted in 1 document being updated.

You could also pull an array of _ids that you'd like to update then run the update query using $in. This will require two calls to the mongo however the updates will still be atomic:
Question.find().select("_id").limit(4).exec(function(err, questions) {
var q = Question.update({_id: {$in: questions}}, {
$inc: {answerCount: 1, lockedCount:1},
$push: {devices: deviceid}
}, {multi:true});
q.exec(function(err) {
console.log("Done");
});
});

So I did an simple map implementation and will use it unless someone can find a more efficient way to do it.
Question
.find({
devices: { $ne: deviceID}
},
{ name: true, _id: true})
.sort({
answerCount: 1,
lockedCount: 1
})
.limit(req.query.limit || 4)
.exec(updateAllFound );
function updateAllFound(err, questions) {
if (err) {
return handleError(res, err);
}
var ids = questions.map(function(item){
return item._id;
});
return Question.update({ _id: { $in: ids} } ,
{
$inc: {
answerCount: 1,
lockedCount: 1
},
$push:{
devices: deviceID
}
}, { multi: true }, getByDeviceID);
function getByDeviceID(){
return res.json(200, questions);
}
}

Related

Conditionally update item or delete item in one query in mongoose

So, what I'm trying to achieve here is two things. First one is to decrement the quantity of a product. Second is that if that product quantity is 0 then delete that product.
So I'm trying to set a condition that if product quantity is 0 then delete that product otherwise simply decrement the quantity.
Here is my cart document:
And here is my function:
const cart = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{$lt: ['items.productID', 0] ? { $pull: { items: { productID: req.body.productID } } } : { $inc: { 'items.$.quantity': -1 } }},
{ new: true }
);
I have tried to achieve that with ternary operator but it didn't worked. Can anybody help me with this please? Thank you.
You cannot use a ternary operator in your query. Not only is this not supported in MongoDB, it will also simply be evaluated by node.js, not by MongoDB itself. In fact, conditional update operations of the nature you're describing are themselves not something that MongoDB is capable of handling.
Your best bet is to perform two queries, where the first one performs the decrement operation and the second pulls any items out that have a quantity less than or equal to 0:
const cartAfterDecrement = await Cart.findOneAndUpdate(
{ userID: req.body.userID, "items.productID": req.body.productID },
{ $inc: { 'items.$.quantity': -1 } },
{ new: true }
);
const cartAfterPull = await Cart.findOneAndUpdate(
{ userID: req.body.userID, items: { $elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
} } },
{ $pull: {items: { quantity: { $lte: 0 } } } },
{ new: true }
);
const cart = cartAfterPull ? cartAfterPull : cartAfterDecrement;
If you wish to allow products with quantities equal to 0, then modifying the second query is a trivial matter.
The only downside to this approach is that there will be a natural, very slight delay between these two updates, which may result in a very rare case of a race condition occurring where a cart is returned and contains an item with a quantity less than or equal to 0. In order to avoid this, you will need to either filter these values out with server code or by retrieving your cart(s) using aggregation, or you will need to make use of transactions to ensure that the two operations occur in sequence without the document being retrieved between the two write operations.
With that said, constructing the appropriate array filtering logic or transaction management is beyond the scope of this question, so will not be included in this answer.
Query
you can do it with a pipeline update ( >= MongoDB 4.2)
find the user that has that productID
reduce the items, to an array with item but
if productID not the one i look for, keep it as it is
else if quantity=0 remove it(don't add it to the reduced array)
else keep it with quantity-1
*findAndModify accepts pipeline also, for nodejs driver you must find the method that wraps this command, or run the command directly
Test code here
update(
{"userID": req.body.userID,
"items.productID": req.body.productID},
[{"$set":
{"items":
{"$reduce":
{"input":"$items",
"initialValue":[],
"in":
{"$switch":
{"branches":
[{"case":{"$ne":["$$this.productID", req.body.productID]},
"then":{"$concatArrays":["$$value", ["$$this"]]}},
{"case":{"$gt":["$$this.quantity", 0]},
"then":
{"$concatArrays":
["$$value",
[{"$mergeObjects":
["$$this", {"$subtract":["$this.quantity", 1]}]}]]}}],
"default":"$$value"}}}}}}])
Thanks #B. Fleming and all others for your answer. I have just use a simple code implementation for that however it wasn't what I was expected but still it's good to go.
module.exports.deleteCartItem = async (req, res, next) => {
var condition;
var update;
const product = await Cart.findOne({ userID: req.body.userID,
items: {
$elemMatch: {
productID: req.body.productID,
quantity: { $lte: 0 }
}
}
});
// IF PRODUCT WITH 0 QUANTITY PULL/DELETE THAT PRODUCT
if (product !== null) {
condition = { userID: req.body.userID, items: { $elemMatch: { productID: req.body.productID, quantity: { $lte: 0 } } } },
update = { $pull: {items: { quantity: { $lte: 0 } } } }
}
// OTHERWISE DECREMENT THE QUANTITY FOR THAT PRODUCT
else {
condition = { userID: req.body.userID, "items.productID": req.body.productID }
update = { $inc: { 'items.$.quantity': -1 } }
}
try {
const cart = await Cart.findOneAndUpdate(condition, update, { new: true });
if (cart !== null) {
res.status(200).json({
statusCode: 1,
message: "Item Removed",
responseData: cart
});
} else {
res.json({
statusCode: 0,
msgCode: 462,
message: 'Not found',
responseData: null
});
}
} catch (err) {
console.log('Server Error: ', err);
next(new Error('Server Error, Something was wrong!'));
}
}

Mongoose $inc with maximum value

So I'm currently trying to perform this operation
return this.model.findByIdAndUpdate(id, { $push: { certifiedBy: certifier } }, { $inc: {score: 1}}, { new: true })
The issue here is that score will grow without limit, I would like to prevent this and make it so when this is happening it cannot increment if score <= 5 but still add certifier into my certifiedBy array.
Can it be done with mongoose directly or do I have to get the object first check if it over 5 and call a different query in that case ?
Thanks
You can't change the $inc behaviour but you can do a checkpoint to stop it before 5
return this.model.findOneAndUpdate({
_id: id,
score: {
$lte: 5
}
}, {
$push: { certifiedBy: certifier },
$inc: { score: 1 }
},
{
new: true
})

Check for subdocument is deleted or not in node.js [duplicate]

Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
if(err) console.log(err);
console.log("DOC " + doc)
if(doc.status) {
// FOUND ROOM SATTUS IS TRUE LOGIC
console.log(doc);
// return callback(true)
}
});
Above query will return to me the actual document that's updated or inserted but I can't check exactly which one it is. If I do an update instead of findOneandUpdate I'm returned this
{
ok: 1,
nModified: 0,
n: 1,
upserted: [ { index: 0, _id: 55df883dd5c3f7cda6f84c78 } ]
}
How do I return both the document and the write result or at least the upserted field from the write result.
As of 8 August 2019 (Mongoose Version 5.6.9), the property to set is "rawResult" and not "passRawResult":
M.findOneAndUpdate({}, obj, {new: true, upsert: true, rawResult:true}, function(err, d) {
if(err) console.log(err);
console.log(d);
});
Output:
{ lastErrorObject:
{ n: 1,
updatedExisting: false,
upserted: 5d4befa6b44b48c3f2d21c75 },
value: { _id: 5d4befa6b44b48c3f2d21c75, rating: 4, review: 'QQQ' },
ok: 1 }
Notice also the result is returned as the second parameter and not the third parameter of the callback. The document can be retrieved by d.value.
Version 4.1.10 of Mongoose has an option called passRawResult which if set to true causes the raw parameter to be passed. Leaving out this option seems to default to false and cause raw to always be undefined:
passRawResult: if true, passes the raw result from the MongoDB driver
as the third callback parameter
http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate
Alright so my main problem was that I couldn't get the _id of the document I inserted without not being able to check whether if it was updated/found or inserted. However I learned that you can generate your own Id's.
id = mongoose.Types.ObjectId();
Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {_id: id, status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
if(err) console.log(err);
if(doc === null) {
// inserted document logic
// _id available for inserted document via id
} else if(doc.status) {
// found document logic
}
});
Update
Mongoose API v4.4.8
passRawResult: if true, passes the raw result from the MongoDB driver as the third callback parameter.
I'm afraid Using FindOneAndUpdate can't do what you whant because it doesn't has middleware and setter and it mention it the docs:
Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:
defaults
Setters
validators
middleware
http://mongoosejs.com/docs/api.html search it in the findOneAndUpdate
if you want to get the docs before update and the docs after update you can do it this way :
Model.findOne({ name: 'borne' }, function (err, doc) {
if (doc){
console.log(doc);//this is ur document before update
doc.name = 'jason borne';
doc.save(callback); // you can use your own callback to get the udpated doc
}
})
hope it helps you
I don't know how this got completely off track, but there as always been a "third" argument response to all .XXupdate() methods, which is basically the raw response from the driver. This always tells you whether the document is "upserted" or not:
Chatrooms.findOneAndUpdate(
{ "Roomname": room.Roomname },
{ "$setOnInsert": {
"status": true, "userNum": 1
}},
{ "new": true, "upsert": true },
function(err, doc,raw) {
if(err) console.log(err);
// Check if upserted
if ( raw.lasErrorObject.n == 1 && !raw.lastErrorObject.updatedExisting ) {
console.log("upserted: %s", raw.lastErrorObject.upserted);
}
console.log("DOC " + doc)
if (doc.status) {
// FOUND ROOM SATTUS IS TRUE LOGIC
console.log(doc);
// return callback(true)
}
});
Which will tell you the _id of the document that was just upserted.
From something like this in the "raw" response:
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e12c65f6044f57c8e09a46 },
value: { _id: 55e12c65f6044f57c8e09a46,
status: true,
userNum: 1
__v: 0 },
ok: 1 }
Complete reproducible listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var testSchema = new Schema({
name: String
});
var Test = mongoose.model('Test', testSchema, 'test');
async.series(
[
function(callback) {
Test.remove({},callback);
},
function(callback) {
async.eachSeries(
["first","second"],
function(it,callback) {
console.log(it);
Test.findOneAndUpdate(
{ "name": "Bill" },
{ "$set": { "name": "Bill" } },
{ "new": true, "upsert": true },
function(err,doc,raw) {
console.log(raw),
console.log(doc),
callback(err);
}
);
},
callback
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Which outputs:
first
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e2a92328f7d03a06a2dd6b },
value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }
second
{ lastErrorObject: { updatedExisting: true, n: 1 },
value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }

Updating two different documents in one line

I have the following code for updating two users, both with different id .
User.update(
{ id: req.body.myId },
{
$addToSet: { FriendIds: req.body.friendId }
},
function(err, user){
}
);
User.update(
{ id: req.body.friendId },
{
$addToSet: { FriendIds: req.body.myId }
},
function(err, user){
}
);
I could not find anything in the docs aside from updating two things with the same attribute by setting multi : true . However, the ids are different in this case, and it would be easier to error handle if I had them both being updated at once.
Why not take advantage of parallel operations in bluebird or using co
You can do something like :
co(function* () {
var res = yield {
1: User.update({ id: req.body.myId }, { $addToSet: { FriendIds: req.body.friendId }}).exec(),
2: User.update({ id: req.body.friendId },{$addToSet: { FriendIds: req.body.myId }}).exec(),
};
console.log(res); // { 1: Upate_response_for_1 , 2: Upate_response_for_2 }
}).catch(onerror);

How to check whether a document was inserted or updated when using findOneAndUpdate?

Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
if(err) console.log(err);
console.log("DOC " + doc)
if(doc.status) {
// FOUND ROOM SATTUS IS TRUE LOGIC
console.log(doc);
// return callback(true)
}
});
Above query will return to me the actual document that's updated or inserted but I can't check exactly which one it is. If I do an update instead of findOneandUpdate I'm returned this
{
ok: 1,
nModified: 0,
n: 1,
upserted: [ { index: 0, _id: 55df883dd5c3f7cda6f84c78 } ]
}
How do I return both the document and the write result or at least the upserted field from the write result.
As of 8 August 2019 (Mongoose Version 5.6.9), the property to set is "rawResult" and not "passRawResult":
M.findOneAndUpdate({}, obj, {new: true, upsert: true, rawResult:true}, function(err, d) {
if(err) console.log(err);
console.log(d);
});
Output:
{ lastErrorObject:
{ n: 1,
updatedExisting: false,
upserted: 5d4befa6b44b48c3f2d21c75 },
value: { _id: 5d4befa6b44b48c3f2d21c75, rating: 4, review: 'QQQ' },
ok: 1 }
Notice also the result is returned as the second parameter and not the third parameter of the callback. The document can be retrieved by d.value.
Version 4.1.10 of Mongoose has an option called passRawResult which if set to true causes the raw parameter to be passed. Leaving out this option seems to default to false and cause raw to always be undefined:
passRawResult: if true, passes the raw result from the MongoDB driver
as the third callback parameter
http://mongoosejs.com/docs/api.html#query_Query-findOneAndUpdate
Alright so my main problem was that I couldn't get the _id of the document I inserted without not being able to check whether if it was updated/found or inserted. However I learned that you can generate your own Id's.
id = mongoose.Types.ObjectId();
Chatrooms.findOneAndUpdate({Roomname: room.Roomname},{ $setOnInsert: {_id: id, status: true, userNum: 1}}, {new: true, upsert: true}, function(err, doc) {
if(err) console.log(err);
if(doc === null) {
// inserted document logic
// _id available for inserted document via id
} else if(doc.status) {
// found document logic
}
});
Update
Mongoose API v4.4.8
passRawResult: if true, passes the raw result from the MongoDB driver as the third callback parameter.
I'm afraid Using FindOneAndUpdate can't do what you whant because it doesn't has middleware and setter and it mention it the docs:
Although values are cast to their appropriate types when using the findAndModify helpers, the following are not applied:
defaults
Setters
validators
middleware
http://mongoosejs.com/docs/api.html search it in the findOneAndUpdate
if you want to get the docs before update and the docs after update you can do it this way :
Model.findOne({ name: 'borne' }, function (err, doc) {
if (doc){
console.log(doc);//this is ur document before update
doc.name = 'jason borne';
doc.save(callback); // you can use your own callback to get the udpated doc
}
})
hope it helps you
I don't know how this got completely off track, but there as always been a "third" argument response to all .XXupdate() methods, which is basically the raw response from the driver. This always tells you whether the document is "upserted" or not:
Chatrooms.findOneAndUpdate(
{ "Roomname": room.Roomname },
{ "$setOnInsert": {
"status": true, "userNum": 1
}},
{ "new": true, "upsert": true },
function(err, doc,raw) {
if(err) console.log(err);
// Check if upserted
if ( raw.lasErrorObject.n == 1 && !raw.lastErrorObject.updatedExisting ) {
console.log("upserted: %s", raw.lastErrorObject.upserted);
}
console.log("DOC " + doc)
if (doc.status) {
// FOUND ROOM SATTUS IS TRUE LOGIC
console.log(doc);
// return callback(true)
}
});
Which will tell you the _id of the document that was just upserted.
From something like this in the "raw" response:
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e12c65f6044f57c8e09a46 },
value: { _id: 55e12c65f6044f57c8e09a46,
status: true,
userNum: 1
__v: 0 },
ok: 1 }
Complete reproducible listing:
var async = require('async'),
mongoose = require('mongoose'),
Schema = mongoose.Schema;
mongoose.connect('mongodb://localhost/test');
var testSchema = new Schema({
name: String
});
var Test = mongoose.model('Test', testSchema, 'test');
async.series(
[
function(callback) {
Test.remove({},callback);
},
function(callback) {
async.eachSeries(
["first","second"],
function(it,callback) {
console.log(it);
Test.findOneAndUpdate(
{ "name": "Bill" },
{ "$set": { "name": "Bill" } },
{ "new": true, "upsert": true },
function(err,doc,raw) {
console.log(raw),
console.log(doc),
callback(err);
}
);
},
callback
);
}
],
function(err) {
if (err) throw err;
mongoose.disconnect();
}
);
Which outputs:
first
{ lastErrorObject:
{ updatedExisting: false,
n: 1,
upserted: 55e2a92328f7d03a06a2dd6b },
value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }
second
{ lastErrorObject: { updatedExisting: true, n: 1 },
value: { _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 },
ok: 1 }
{ _id: 55e2a92328f7d03a06a2dd6b, name: 'Bill', __v: 0 }

Resources