Using Meteor to do $push and $set at the same time - javascript

Is it possible to use Meteor to do both a $push and a $set in the same operation?
I want it to be in the same operation so a cursor.observe will not be triggered twice by splitting up into 2 different MongoDB operations.
Currently, the following fails to insert into the array:
Animals.update(
{_id: animal_id},
{
$set: {
driver_id: '',
status: 'feeding'
},
$push: {
feeder: Meteor.user()._id
}
}
)
For $push to work, it needs to be split into 2:
Animals.update(
{_id: animal_id},
{ $set:
{
driver_id: '',
status: 'feeding'
}
}
Animals.update(
{_id: animal_id},
{
$push: {
feeder: Meteor.user()._id
}
}

You can combine both into a $set by extracting the array first, pushing onto it, then including it.
const animal = Animals.findOne(animal_id);
let feeder = [] && animal.feeder;
feeder.push(Meteor.userId);
Animals.update(animal_id,
{
$set: {
driver_id: '',
status: 'feeding'
feeder: feeder
}
}
)

Multiple updates may be performed on the same document, as long as those updates do not conflict (hence the "have conflicting mods in update" error).
Because "$push" : {"bugs" : [{"name":"bug1", "count":1}]} and "$inc" : {"bugs.0.count" : 1} are both attempting to modify the same portion of the document (namely the "bugs" array), they conflict.
Multiple updates may be combined if each affects a different part of the document:

Related

Writing data to a Mongodb or updating it, if it's already in the database

So I am trying to make a level system with Node.JS, Discord.js, and MongoDB, but I can't figure out, how to add/update data in the database.
Here is the code I use, but it doesn't work:
if (db.collection(message.guild.id).findOne({ user: message.author.id })) {
db.collection(message.guild.id).updateOne(
{ user: message.author.id },
{
$set: { lastmessage: Date.now() },
}
);
} else {
db.collection(message.guild.id).insertOne({
user: message.author.id,
lastmessage: Date.now(),
level: 0,
xp: 0,
});
}
Welcome to stackoverflow,
First of all, db.collection(message.guild.id).findOne({user: message.author.id}) returns a promise, so you have to handle it properly either by using .then() or async/await method, or any third party promise manager if that's your stuff.
db.collection(message.guild.id).findOne({user: message.author.id}).then((result)=>{
//Do stuff with result
})
But you do not even need that for your problem : there is a way of updating an object to a MongoDB or insert it if it does not already exists : use the upsert:true option, see documentation :
db.collection.updateOne(
<filter>,
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2.1
}
)
Your code should look like this :
db.collection(message.guild.id).updateOne(
{user: message.author.id},
{$set: { lastmessage: Date.now()}},
{upsert:true}
);

MongoDB: Update many documents with different data in each

I have an array of _id:
ordersId = ["5ec42446347f396fc3d86a3d", "5ec422d4347f396fc3d86a3c", "5ecefaf0aead3070fbdab7dd"]
And I'm trying to update the documents that match these _ids, but with different data in each of them:
const existentOrders = await Orders.updateMany(
{ _id: { $in: ordersId } },
{
$set: {
status: "Reviewing",
cashier: req.id,
rate: dataBasedOnId,
},
}
);
I have an array of objects that also have these ids:
const ordersRates = [
{
_id: "5ec42446347f396fc3d86a3d"
rate: 2434686948.19
},
{
_id: "5ec422d4347f396fc3d86a3c"
rate: 2434686948.19
},
{
_id: "5ecefaf0aead3070fbdab7dd",
rate: 93320.00
}
]
So what I'm trying to do is to update each document with the rate that is in the ordersRate variable.
Is it possible to update them using only one operation?
Thanks!
I would suggest you to use bulk write which will save network round trip time instead using multiple requests.
You have two variables and form list of UpdateOne operations and pass it to bulk write.
Refer

Push "programmatic" array to an Object's array property

I'm building a Thesaurus app, and for this question, the key note is that i'm adding a list of synonyms(words that have the same meaning) for a particular word(eg - "feline", "tomcat", "puss" are synonyms of "cat")
I have a Word object, with a property - "synonyms" - which is an array.
I'm going to add an array of synonyms to the Word synonyms property.
According to the MongoDb documentation see here, the only way to append all the indexes of an array to a document's array property at once is to try the following:
db.students.update(
{ _id: 5 },
{
$push: {
quizzes: {
$each: [ { wk: 5, score: 8 }, { wk: 6, score: 7 }, { wk: 7, score: 6 } ],
}
}
}
)
Let's re-write that solution to suit my data, before we venture further.
db.words.update(
{ baseWord: 'cat' },
{
$push: {
synonyms: {
$each: [ { _id: 'someValue', synonym: 'feline' }, { _id: 'someValue', synonym: 'puss' }, { _id: 'someValue', synonym: 'tomcat' } ],
}
}
}
)
Nice and concise, but not what i'm trying to do.
What if you don't know your data beforehand and have a dynamic array which you'd like to feed in?
My current solution is to split up the array and run a forEach() loop, resulting in an array being appended to the Word object's synonyms array property like so:
//req.body.synonym = 'feline,tomcat,puss';
var individualSynonyms = req.body.synonym.split(',');
individualSynonyms.forEach(function(synonym) {
db.words.update(
{ "_id": 5 },
{ $push: //this is the Word.synonyms
{ synonyms:
{
$each:[{ //pushing each synonym as a Synonym object
uuid : uuid.v4(),
synonym:synonym,
}]
}
}
},{ upsert : true },
function(err, result) {
if (err){
res.json({ success:false, message:'Error adding base word and synonym, try again or come back later.' });
console.log("Error updating word and synonym document");
}
//using an 'else' clause here will flag a "multiple header" error due to multiple json messages being returned
//because of the forEach loop
/*
else{
res.json({ success:true, message:'Word and synonyms added!' });
console.log("Update of Word document successful, check document list");
}
*/
});
//if each insert happen, we reach here
if (!err){
res.json({ success:true, message:'Word and synonyms added!.' });
console.log("Update of Word document successful, check document list");
}
});
}
This works as intended, but you may notice and issue at the bottom, where there's a commented out ELSE clause, and a check for 'if(!err)'.
If the ELSE clause is executed, we get a "multiple headers" error because the loop causes multiple JSON results for a single request.
As well as that, 'if(!err)' will throw an error, because it doesn't have scope to the 'err' parameter in the callback from the .update() function.
- If there was a way to avoid using a forEach loop, and directly feed the array of synonyms into a single update() call, then I can make use of if(!err) inside the callback.
You might be thinking: "Just remove the 'if(!err)' clause", but it seems unclean to just send a JSON response without some sort of final error check beforehand, whether an if, else, else if etc..
I could not find this particular approach in the documentation or on this site, and to me it seems like best practice if it can be done, as it allows you to perform a final error check before sending the response.
I'm curious about whether this can actually be done.
I'm not using the console, but I included a namespace prefix before calling each object for easier reading.
There is not need to "iterate" since $each takes an "array" as the argument. Simply .map() the produced array from .split() with the additional data:
db.words.update(
{ "_id": 5 },
{ $push: {
synonyms: {
$each: req.body.synonym.split(',').map(synonym =>
({ uuid: uuid.v4, synonym })
)
}
}},
{ upsert : true },
function(err,result) {
if (!err){
res.json({ success:true, message:'Word and synonyms added!.' });
console.log("Update of Word document successful, check document list");
}
}
);
So .split() produces an "array" from the string, which you "transform" using .map() into an array of the uuid value and the "synonym" from the elements of .split(). This is then a direct "array" to be applied with $each to the $push operation.
One request.

Mongoose: Incrementing my documents version number doesn't work, and I'm getting a Version Error when I try to save

When I try to save my document, I'm getting a VersionError: No matching document found error, similar to this SO question.
After reading this blog post, it seems that the problem is with the versioning of my document. That I'm messing with an array and so I need to update the version.
However, calling document.save() doesn't work for me. When I log out the document before and after the call to save(), document._v is the same thing.
I also tried doing document._v = document._v++ which also didn't work.
Code
exports.update = function(req, res) {
if (req.body._id) { delete req.body._id; }
User.findById(req.params.id, function(err, user) {
if (err) return handleError(res, err);
if (!user) return res.send(404);
var updated = _.extend(user, req.body); // doesn't increment the version number. causes problems with saving. see http://aaronheckmann.blogspot.com/2012/06/mongoose-v3-part-1-versioning.html
console.log('pre increment: ', updated);
updated.increment();
// updated._v = updated._v++;
console.log('post increment: ', updated);
updated.save(function(err) {
if (err) return handleError(res, err);
return res.json(200, user);
});
});
};
Output
pre increment: { _id: 5550baae1b571aafa52f070c,
provider: 'local',
name: 'Adam',
email: 'azerner3#gmail.com',
hashedPassword: '/vahOqXwCwKQKtcV3KBQeFge/YB0xtqOj+YDyck7gzyALA/IP7u7BfqQhlVHBQT26//XfBTkaOCK2bQXg65OzA==',
salt: 'MvzXW7D4xuyGQBJNeFRoUg==',
__v: 32,
drafts: [],
starredSkims: [],
skimsCreated: [ 5550cfdab8dcacd1a7892aa4 ],
role: 'user' }
post increment: { _id: 5550baae1b571aafa52f070c,
provider: 'local',
name: 'Adam',
email: 'azerner3#gmail.com',
hashedPassword: '/vahOqXwCwKQKtcV3KBQeFge/YB0xtqOj+YDyck7gzyALA/IP7u7BfqQhlVHBQT26//XfBTkaOCK2bQXg65OzA==',
salt: 'MvzXW7D4xuyGQBJNeFRoUg==',
__v: 32,
drafts: [],
starredSkims: [],
skimsCreated: [ 5550cfdab8dcacd1a7892aa4 ],
role: 'user' }
The issue here has to do with using __v and trying to update it manually. .increment does not actually perform an increment immediately, but it does set an internal flag for the model to handle incrementing. I can't find any documentation on .increment, so I assume it is probably for use internally. The problem stems from trying to combine .extend with an object that already has __v (there are two underscores by the way, not that document.__v++ affects the model internally anyway) in addition to using .increment.
When you use _.extend it copies the __v property directly onto the object which seems to cause problems because Mongoose cannot find the old version internally. I didn't dig deep enough to find why this is specifically, but you can get around it by also adding delete req.body.__v.
Rather than finding and saving as two steps, you can also use .findByIdAndUpdate. Note that this does not use __v or increment it internally. As the other answer and linked bug indicate, if you want to increment the version during an update you have to do so manually.
Versioning was implemented to mitigate the doc.save() by design (not Model.update etc). But if you want you can try the following instead:
{$set: {dummy: [2]}, $inc: { __v: 1 }}
However this was a confirmed-bug according to the link
Please validate your mongoose version from the milestone of the above issue.
Thanks :)

MongoDB $addtoSet and $set in the same operation

In my Meteor/javascript app, I am trying to update a specific MongoDB document via $addToSet on a certain part of that document followed by $set on a different part of that same document. There is no overlap between these two portions, so from what I read online it should be safe. But I can't seem to get the syntax correct. How can I carry out these two operations in one javascript command? The below works as two separate commands, and I assume it would be faster if they could be combined into just one write to Mongo.
Collection.update(
{_id: documentId},
{$addToSet:
{data:
{$each: newData}
}
}
);
Collection.update(
{_id: documentId},
{$set:
{lastTxn: lastTxn,
updatedAt: new Date()}
}
);
As long as they are not on the same path ( which is the one thing you cannot do ) then it is perfectly fine. The $set and $addToSet operations are just top level "keys" of the "update document":
Collection.update(
{ "_id": documentId },
{
"$addToSet": { "data": { "$each": newData } },
"$set": {
"lastTxn": lastTxn,
"updatedAt": new Date()
}
}
);

Categories