$ projection in mongoDB findOneAndUpdate() - node.js

I'm trying to build a simple task queue with express and mongoose. The idea is acquire a single client and return campaign id and client id (which is a subdocument of campaign). Each time someone acquires a client, its status code is set to 1. I've come up with the following query:
router.post('/lease', (err, res) => {
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
new: true,
projection: {
'clients.$': true,
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
});
But all i'm getting after connecting to this endpoint is this:
{"_id":"591483241a84946a79626aef","clients":[{},{}]}
It seems to me that the problem is with the $ projection, but I have no idea how to fix this.
EDIT: I tried using the following code, utilizing $elemMatch:
router.post('/lease', (err, res) => {
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
new: true,
projection: {
clients: {
'$elemMatch': {contact_status_code: 1},
}
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
});
Unfortunately, each request yields the first subdocument in the collection, that matched the criteria -- not specifically the one that was updated. Here is an example:
Say, i have the following document in mongo:
{
"_id" : ObjectId("591493d95d48e2738b0d4317"),
"name" : "asd",
"template" : "{{displayname}}",
"isEnabled" : true,
"clients" : [
{
"displayname" : "test",
"_id" : ObjectId("591493d95d48e2738b0d4319"),
"contact_status_code" : 0
},
{
"displayname" : "client",
"_id" : ObjectId("591493d95d48e2738b0d4318"),
"contact_status_code" : 0
}
],
"__v" : 0
}
I run the query for the first time and get the following result:
{"_id":"591493d95d48e2738b0d4317","clients":[{"displayname":"test","_id":"591493d95d48e2738b0d4319","contact_status_code":1}]}
Notice client id "591493d95d48e2738b0d4319" -- this time it runs as expected. But when i run the same query the second time, I get absolutely the same object, although I expect to get one with id "591493d95d48e2738b0d4318".

The issue was with new: true
Here is a working example:
Campaign.findOneAndUpdate({'isEnabled': true, 'clients.contact_status_code': 0}, {
'$set': { 'clients.$.contact_status_code': 1 },
},
{
//new: true <-- this was causing the trouble
projection: {
clients: {
'$elemMatch': {contact_status_code: 0}, // 0 because the old record gets matched
},
},
},
(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
I assume, when the new:true is set, mongo loses the matching context. This approach returns the old record, unfortunately, but that still serves my needs to get the _id.

Seems to me you are getting the campaign id, but you also want clients.$, have you tried clients.$._id?

Update for node MongoDB 3.6 driver
unlike findAndModify the function findOneAndUpdate doesn't have the option new in its options list
But you can use instead returnDocument
returnDocument accept before OR after
When set to after,returns the updated document rather than the original
When set to before,returns the original document rather than the updated.
The default is before.
returnOriginal is Deprecated Use returnDocument instead
Here is a working example:
const filter={'isEnabled': true, 'clients.contact_status_code': 0}
const update={'$set': { 'clients.$.contact_status_code': 1 }}
const options={
returnDocument: 'after' //returns the updated document
projection: {
clients: {
'$elemMatch': {contact_status_code: 0},
// 0 because the old record gets matched
},
},
}
Campaign.findOneAndUpdate(filter,update, options
,(err, campaign) => {
if (err) {
return res.send(err);
}
res.json(campaign);
}
);
findOneAndUpdate Node.js MongoDB Driver API 3.6 documentation

Related

How to delete object inside an array in mongodb collection?

i have a controller function as:
User.updateOne(
{ _id: "6252d87e010b3f94adcf4e41" },
{
$pull: {
newCart: {
"post._id": "6249e216c1e3b6ae2d4aa273",
},
},
},
{new:true}
).then((res) => console.log(res, user));
The response is like this:
{
acknowledged: true,
modifiedCount: 1,
upsertedId: null,
upsertedCount: 0,
matchedCount: 1
}
It shows modifiedCount to 1 but never changes in my realtime database collection.
My collection looks like this:
Image of my db collection
The query is correct maybe try adding exec
User.updateOne(
{ _id: "6252d87e010b3f94adcf4e41" },
{
$pull: {
newCart: {
"post._id": "6249e216c1e3b6ae2d4aa273",
},
},
},
{ new: true }
)
.exec()
.then((res) => console.log(res, user));
Hope this will solve the issue.
your query is completely fine. disconnect your database and try to connect again. after restart maybe this will work

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 }

Mongoose update returns undefined

How can I update a field with new properties that is initially set to be an empty object?
For example, I have the following schema:
import mongoose from 'mongoose';
var RunSchema = mongoose.Schema(
{
runId: { type: String },
reports: {
cookieSummary: {
name: String,
path: String
}
}
}
)
export default mongoose.model('Run', RunSchema);
And I'm trying to update the following document:
{
"_id": {
"$oid": "5a0565c2537e0b5d9d08ee6b"
},
"__v": 0,
"reports": {},
"runId": "8r4LNN3fRqd3qNgdW"
}
But when I run this code, it returns undefined:
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {'reports.cookieSummary': { 'name': 'test' }},
}, (err, doc) => { console.log(doc) })
The object notation works after adding type to fields, like this: name: { type: String }
Try to use dot notation, as you're setting just one field:
Run.findOneAndUpdate(
{ runId: '8r4LNN3fRqd3qNgdW' },
{ $set: {'reports.cookieSummary.name': 'test' } },
(err, doc) => { console.log(doc) })
According to the docs, the command you're using should work but you write it wrongly. Try like this:
Run.findOneAndUpdate(
{ runId: '8r4LNN3fRqd3qNgdW' },
{ $set: { 'reports.cookieSummary': {'name': 'test'} } },
(err, doc) => { console.log(doc) })
if it does not work, maybe mongo expect that the object matches its schema when you use the command like this. But I don't think so.
Let me know.
Your query for update a document is good only the mistake is at the end of curly braces of $set. You entered un-necessary comma at the end that is actually creating problem in this case. So I suggest you to remove it and run this :
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {'reports.cookieSummary': { 'name': 'test' }}
}, (err, doc) => { console.log(doc) });
and then see. Rest of your query is fine.
Hope It will work for you.
Thanks.
Try using below code, it will update the document and return the updated document.
var Q = require('q');
var deferred = Q.defer();
Run.findOneAndUpdate({ runId: '8r4LNN3fRqd3qNgdW' }, { $set: { 'reports.cookieSummary.name': 'test' } }, { new: true },
(err, doc) => {
console.log(doc);
deferred.resolve(doc);
});
return deferred.promise;
I made a small change. Test this solution.
Run.findOneAndUpdate({runId: '8r4LNN3fRqd3qNgdW'},
{
$set: {"reports": {'cookieSummary':{'name': 'test'}}},
}, (err, doc) => { console.log(doc) })

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 }

Mongoose update with limit

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);
}
}

Resources