How to update a collection document using multiple criteria - node.js

I have this document in collection "registosSRS":
{
"_id" : ObjectId("5a3a2b47d04b7e07f8a273dc"),
"sessao" : "5",
"valorRelacao" : "4.89",
"valorObjectivo" : "4.97",
"valorAbordagem" : "4.88",
"valorGeral" : "4.92",
"cliente_id" : "5a1407c8099ca208e48170a5",
"email" : "mgoncalves#psi.uminho.pt",
"data" : 1513761607431
}
and this document in collection sessao. I've created this document in another place, and set dadosSRS to {} because I want to later change this value. Is this possible to add this value without having created it?
"_id" : ObjectId("5a3a2b41d04b7e07f8a273db"),
"cliente" : ObjectId("5a1407c8099ca208e48170a5"),
"data" : 1513761601705,
"numero" : "5",
"dadosORS" : ObjectId("5a3a2b41d04b7e07f8a273da"),
"dadosSRS" : {
}
Then I'm looking in collection sessao for a client and number os session as in registosSRS, to add the registosSRS id.
mongoClient.collection('sessao', function(err,collection){
collection.update(
{cliente:result.cliente_id, numero:dadosSRS.sessao},
{$set: {'dadosSRS':dadosSessao.dadosSRS}},
function(result){
if (err) throw err;
console.log(result);
console.log('encontrou registo do cliente na collection registoSRS: ' + result);
});
but the result is null, although I have the client and the session number. What am I doing wrong?

In your callback you have only one argument which is essentially the error object hence the result is null since there is no error thrown from the update option. You need a second argument which returns the actual result object. You are using the wrong update method which returns the result object if the command was executed successfully, not the actual document.
From the documentation, the result object has the following properties:
Name Type Description
result Object The raw result returned from MongoDB, field will vary depending on server version.
Properties
Name Type Description
ok Number Is 1 if the command executed correctly.
n Number The total count of documents scanned.
nModified Number The total count of documents modified.
connection Object The connection object used for the operation.
matchedCount Number The number of documents that matched the filter.
modifiedCount Number The number of documents that were modified.
upsertedCount Number The number of documents upserted.
upsertedId Object The upserted id.
You instead need to use findOneAndUpdate which will return the updated document if found
const { ObjectId } = require('mongodb'); // or ObjectID
// or var ObjectId = require('mongodb').ObjectId if node version < 6
const safeObjectId = s => ObjectId.isValid(s) ? new ObjectId(s) : null;
collection.findOneAndUpdate(
{ 'cliente': safeObjectId(result.cliente_id), 'numero': dadosSRS.sessao },
{ '$set': { 'dadosSRS': dadosSessao.dadosSRS } },
{ 'returnOriginal': true },
function(err, result) { // <-- add result as a second argument in the callback
if (err) throw err;
console.log(result);
console.log('encontrou registo do cliente na collection registoSRS: ' + result);
});

Related

Persist rest of the object when updating fields with $set- mongoose

Below is my collection structure :
{
"_id" : ObjectId("59c9e021af4886c9149a28c7"),
"userkey" : "r1Et2ZNdW",
"devicekey" : "12345",
"analog" : {
"4" : 458,
"6" : 448,
"7" : 100
},
"__v" : 0
}
My update code for the collection is :
DeviceModel.update({_id : device.id}, {$set : {"analog" : analog}}, function(err, doc){
if(err) throw err;
console.log("Device switches updated");
});
I want to update the one (or more) value of "analog" object. I may or may not have entire value of the "analog" collection. Which means most of the time I will be having just {"7" : 200} or {"6" : 500}...etc. If I use this in my above update code it works. But the problem is rest of the items in the object will be removed. That is, the "analog" object will become just {"7" : 200}. What change should I do to my update code to persist the rest of the Object.? or do I need to change my entire logic ?
You need to first flatten the analog variable object into another object with a property that uses the dot notation. This enables the $set operator to correctly update the embedded field denoted in the dot notation without updating/removing/affecting the other fields.
So, in your example you can go about this:
let setObject = {};
Object.keys(analog).forEach(function (key) {
setObject[`analog.${key}`] = analog[key];
});
DeviceModel.update(
{ '_id': device.id },
{ '$set': setObject },
function(err, doc) {
if(err) throw err;
console.log("Device switches updated");
}
);

Getting error "pipeline element 3 is not an object error", while trying to find & update using aggregate

I am using node js mongodb driver & trying to update an object array inside an object array in a document.
The schema of the document collection is this :
What I Want :
For collection with order no = 1 & items.qty=2 & tax rate = 25, update the tax to "cst" & taxratetype to "flat".
What I Tried :
db.OrderInfo.aggregate(
{$match:{"orderno":"1"}},
{$unwind:'$items'},
{ $match: { 'items.qty' : 2}
},function(err,result1){
if(err){
throw(err);
}else{
indexes = result1[0].items.taxes.map(function(obj, index) {
if(obj.taxrate == 25) {
return index;
}
}).filter(isFinite);
var updateData = {};
updateData["items.$.taxes."+indexes[0]+".tax"]="cst";
updateData["items.$.taxes."+indexes[0]+".taxratetype"]="flat";
db.OrderInfo.update({ "orderno":"1",'items.qty': 2,'items.taxes.taxrate': 25 },{$set: updateData },function(err,result2){
console.log(result2);
});
}
});
Currently I am using db.eval to run this script from node but later will change it once I accomplish the same.
Getting this Error :
{"name":"MongoError","message":"Error: command failed: {\n\t\"ok\" :
0,\n\t\"errmsg\" : \"pipeline element 3 is not an
object\",\n\t\"code\" : 15942\n} : aggregate failed
:\n_getErrorWithCode#src/mongo/shell/utils.js:25:13\ndoassert#src/mongo/shell/assert.js:13:14\nassert.commandWorked#src/mongo/shell/assert.js:267:5\nDBCollection.prototype.aggregate#src/mongo/shell/collection.js:1312:5\n_funcs1#:1:31\n","ok":0,"errmsg":"Error:
command failed: {\n\t\"ok\" : 0,\n\t\"errmsg\" : \"pipeline element 3
is not an object\",\n\t\"code\" : 15942\n} : aggregate failed
:\n_getErrorWithCode#src/mongo/shell/utils.js:25:13\ndoassert#src/mongo/shell/assert.js:13:14\nassert.commandWorked#src/mongo/shell/assert.js:267:5\nDBCollection.prototype.aggregate#src/mongo/shell/collection.js:1312:5\n_funcs1#:1:31\n","code":139}
I know from this issue https://jira.mongodb.org/browse/SERVER-831
that I cannot use a direct update command & hence trying this workaround.
Any other approach for such updates is also fine with me.
EDIT :
As per answer given by #titi23, I had tried using [] also inside function.
It did not gave me any error but, my values also did not get updated.
Two problems in the query :
1) You are missing [] in the aggregate query.
2) The update method does not need the tax rate clause. It will find the nested document & the index from aggregate would serve the purpose in update.
Refer aggregate-definition for more info on how to use it.
Syntax - db.collection.aggregate(pipeline, options)
pipeline - array - A sequence of data aggregation operations or stages.
Try the following:-
db.OrderInfo.aggregate([
{$match:{"orderno":"1"}},
{$unwind:'$items'},
{ $match: { 'items.qty' : 2} }]).toArray(
function(err,result1){
if(err){
throw(err);
}
else{
console.log(result[0]); //See is there any record here
indexes = result1[0].items.taxes.map(function(obj, index) {
if(obj.taxrate == 25) {
return index;
}
}).filter(isFinite);
var updateData = {};
updateData["items.$.taxes."+indexes[0]+".tax"]="cst";
updateData["items.$.taxes."+indexes[0]+".taxratetype"]="flat";
db.OrderInfo.update({ "orderno":"1",'items.qty': 2}, /*Remove the tax rate clause from here..*/
{$set: updateData },function(err,result2){
console.log(result2);
});
}
});
It should not throw the error.
EDIT:- Do toArray() with the aggregate, see if it helps. Updated the query already.

mongoose-encryption and updating objects

I know the mongoose-encryption doc states:
update will work fine on unencrypted and unauthenticated fields, but will not work correctly if encrypted or authenticated fields are involved.
And I've observed that when I use the mongoose create method that my fields are encrypted into the _ct field. However if I then use findByIdAndUpdate to update my object I see the fields are created in plain text (as output from mongodb console via find command).
From save
> db.tenants.find().pretty()
{
"_id" : ObjectId("554b7f8e7806c204e0c7589e"),
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0
}
After findByIdAndUpdate
> db.tenants.find().pretty()
{
"_id" : ObjectId("554b7f8e7806c204e0c7589e"),
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0,
"userId" : ObjectId("55268f43cbfc87be221cd611"),
"social" : "123-45-6789",
"last" : "bar",
"first" : "foo"
}
Is there a recommended strategy for updating objects and maintaining the encryption with mongoose-encryption?
As you quoted, the documentation for mongoose-encryption clearly tells that it does not work for update.
https://github.com/joegoldbeck/mongoose-encryption
Mongoose update hook is little tricky as well.
What you can do potentially is model your collection in such a way that fields which needs to be encrypted are a separate collection altogether and in the paren collection just link them via ids.
Person = {
_id: <ObjectId>
name: Blah
..
..
documents: [
{ 'doc_id': <ObjectId1> },
{ 'doc_id': <ObjectId2> },
]
}
Documents = [
{
"_id" : <ObjectId1>,
"_ac" : BinData(0,"YdJjOUJhzDWuDE5oBU4SH33O4qM2hbotQTsF6NzDnx4hWyJfaWQiLCJfY3QiXQ=="),
"_ct" : BinData(0,"YaU4z/UY3djGCKBcgMaNIFHeNp8NJ9Woyh9ahff0hRas4WD80V80JE2B8tRLUs0Qd9B7IIzHsq6O4pYub5VKJ1PIQA+/dbStZpOH/KfvPoDC6DzR5JdoAu+feU7HyFnFCMY81RZeJF5BKJylhY1+mG4="),
"__v" : 0
}
...
...
]
This will increase code reuse as well.
I have implemented an strategy that i don´t think it is most efficient but it works.
I need to have all my data in database encrypted so i can´t use the above approach.
What i did is to create an update function that finds the document i want to modify, then i construct a new schema object and assing the _id of the found document to the new object.
Then i delete the original document and after that save the new object wich has the original _id. The only problem i found is that mongoose throw an error because duplicated _id that is printed in the console but it still works and _id aren´t duplicated.
I have tried replacing the_id and traking the document with another property but it still throw that error, anyway data is stored as expected.
exports.update= (req, res, next) => {
Solucion.findOne({_id: req.params.id})
.then(document => {
if (!document) {
res.status(404).json({
message: notFoundMessage,
data: null,
error: null
})
} else {
const solucion = new Solucion({
_id: document._id,
identificacion: document.identificacion,
informacion: document.informacion,
estado: req.body
})
Solucion.deleteOne({_id: document._id})
.then(() => {return solucion.save()})
.then(result=> {
return res.status(201).json({
message: editedSavedMessage,
data: result,
error: null
});
})
.catch(err => {
errorHandler.errorHandler(err, res);
})
}
})
};
UPDATE 29/07/2020
I have found that if you use the save method using the same _id, data is stored encrypted but Mongo creates your schema structure but with all values set to null.
Beyond that it seems to work as expected as data is not visible in DB.

Filter subdocument and trigger

i have collection of objects inside an invitation, having hard time to filter particular object and trigger it's boolean field.
Document:
"Invitation" : [
{
"__v" : 0,
"userID" : ObjectId("54afaabd88694dc019d3b628"),//ObjectId of personA
"__t" : "USER",
"_id" : ObjectId("54b5022b583973580c706784"),
"Accepted" : false
},
{
"__v" : 0,
"userID" : ObjectId("54af6ce091324fd00f97a15f"),//ObjectId of personB
"__t" : "USER",
"_id" : ObjectId("54bde39cdd55dd9016271f14"),
"Accepted" : false
}
]
here i have only two objects inside Invitation array,it can be more than two.
Let's say personA and personB send me Invitation, so two different invitation objects are inserted into database having different fields, with objectId of both persons(userID in above document), now if i accept only invitation of personA, it should trigger accepted field of personA object only, here is what i tried so far, but not working as per expectation.
Controller:
User.find({_id: req.user._id},'Invitation',function(err,docs) {
if (err) {
console.log(err);
}
var results = [];
async.each(docs,function(doc,callback) {
async.each(doc.Invitation,function(invite,callback) {
User.findOneAndUpdate(
{'_id': doc._id, 'Invitation._id': invite._id},
{'$set': {'Invitation.$.Accepted': !invite.Accepted}},
function(err,doc) {
results.push(doc);
callback(err);
}
);
},callback);
},function(err) {
if (err)
console.log(err);
console.log('end'+results);
});
});
finally i am looking for a query which can be used to filter a single element or object, like if i accept invitation of personA then Accepted field of personA object should be set to true.
i would be really helpful if some logic is provided.
Thank you
Not a very clear question. But it seems all you really need to do here is just match the only sub-document you want to update in the first place:
User.find(
{
"_id": "req.user._id",
"Invitation._id": personA.id
},
{ "Invitation.$": 1 },
function(err,docs) {
// and continue
}
);
This is the form of the positional $ operator in a "projection" context. Where only the "singular" matched element is returned.
Once you have a "singular" result, then all the other code works as designed.
I should know after all because I wrote it for you. Not that you are paying any decent respect to that.
Update on Aggregate in Mongodb
Toggle boolean value of subdocuments
Or personA.userID or whatever makes it work.
Just use the unique identifier for the "user" where you expect that to match the query conditions.
You can do this:
db.user.update({"invitation.userID": 1}, {"$set" : {"invitation.$.Accepted" : true}});
Replacing the value 1 with the user ID you want to update.
The code is in the syntax of MongoShell, simply convert to driver syntax you are using
The operator used was the $. According to the documentation: The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
For more details see: http://docs.mongodb.org/manual/reference/operator/update/positional/

$inc with mongoose ignored

I am using the following code to add some content in an array and increment two different counters.
The item is properly pushed in the array, and the pendingSize is properly incremented. But the unRead is never incremented. It used to increment, then today it stopped. The value of the unRead field in my mongodb collection ( hosted on mongohq ) is set to 0 (numerical, not string)
When I look in my console, I see 'update success'.
any clue why it stopped working ?
Thanks
Notifications.update({ "id" : theid}, { $push: { pending: notification}, $inc: { unRead : 1 }, $inc: { pendingSize: 1 }}, function(err){
if(err){
console.log('update failed');
callback("Error in pushing." + result.members[i]);
}
else{
console.log('update succes');
callback("Success");
}
});
Combine the args of $inc into a single nested object, like this:
$inc: { unRead : 1, pendingSize: 1 }
Objects represented in JSON are key:value, where the keys must be unique, so trying to specify multiple values for $inc won't work.

Resources