mongoose update inside array object - mongodb

How to update mongodb based
image
need to update in_state Object based on testid and user_id
code
const test = await usertest.update(
{ "testid" :"oOEbG3ycsl5ZIPrFNk172SVma0zTwD6xHvpUM4JWKAj9fg18nRCtq1B0XLdYeuiQh","user_id":"63297394d10aa70d52708a4c"},
{
$set: {
'testdata.1.in_state': in_state
}
}
)
how can achieve this

Try referencing the testdata elements with $:
const test = await usertest.update(
{
testid: 'oOEbG3ycsl5ZIPrFNk172SVma0zTwD6xHvpUM4JWKAj9fg18nRCtq1B0XLdYeuiQh',
user_id: '63297394d10aa70d52708a4c',
},
{
$set: {
'testdata.$.in_state': in_state,
},
}
);

Related

Mongo DB - How to get only the inserted Object in an array when calling collection.watch()

I am having a little problem with my code.
I have a collection called user_relations and there I save friends that a specific user has.
One document example:
So my question is: How can I only get the Object item that has been added to arrayOfFriends without getting all of the other values inside that array and outside. For example:
If I insert {"userId" : "2", "lastMessage" : "Hello"} into arrayOfFriends, I want that my response looks like this: {"userId" : "2", "lastMessage" : "Hello"} and not like this {"_id" : ObjectId("id..."), "arrayOfFriends" : {...}}.
The code that I am currently using:
var stream = db.collection('user_relations').watch(<Map<String, Object>>[
{
'$match': {
'$and': [
{'operationType': 'insert'},
{'fullDocument.userId': '6fcfd7b3847dd9999430f1ad'}
]
}
}
]);
stream.listen((changeEvent) {
Map fullDocument = changeEvent.fullDocument;
print('fullDocument: $fullDocument');
// Insert your logic here
});
The code is written in flutter, but I think it is pretty similar to Node.js and Python and can be read easily.
Thank You!
I believe the OP was asking about how to create this using Flutter. Nonetheless here is a node example not using mongoose, but only using the raw driver...
npm init -y
npm install mongodb
Create file app.js
const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const uri = "mongodb://barry:barry#localhost:50011,localhost:50012,localhost:50013/nodetest?replicaSet=replSet&authSource=admin";
const client = new MongoClient(uri, { useUnifiedTopology: true });
const collectionName = "user_relations";
client.connect(function(err) {
const db = client.db("nodetest");
const collection = db.collection(collectionName);
const pipeline = [
{
"$match": {
"operationType": "insert",
"fullDocument.userId": "6fcfd7b3847dd9999430f1ad"
}
}
];
const stream = collection.watch(pipeline);
const changeStreamOptions = { fullDocument: "updateLookup" };
stream.on("change", stream_OnChange, changeStreamOptions);
});
function stream_OnChange(documentChange) {
console.log(documentChange.fullDocument.arrayOfFriends);
}
Probably need to change the connection string, and/or database names.
Execute using nodemon...
nodemon app.js
Insert into the MongoDB database using mongoshell...
db.user_relations.insert({userId: "6fcfd7b3847dd9999430f1ad", arrayOfFriends: [ {userId: 1, lastMessage: "Message 1"}, {userId: 1, lastMessage: "Message 2"}] })
See output in nodemon window
[nodemon] starting `node app.js`
[
{ userId: 1, lastMessage: 'Message 1' },
{ userId: 1, lastMessage: 'Message 2' }
]
Conclusion and Evaluation
Notice the output is only part of the full document? This is because the stream_OnChange method is referring to the sub fields directly. Is this what you are after?
EDIT 2021-08-29
Per comments by the OP, the desire is to see an item that is added to an array, presumably by a $push operation, but show only that item. The strategy for this is to NOT look at the object "fullDocument" but instead look at the operation in the changestream.
To illustrate this, I have modified the function stream_OnChange(documentChange) as described above in the file app.js.
function stream_OnChange(documentChange) {
console.log("##### FULL PAYLOAD #####");
console.log(documentChange);
console.log("##### END FULL PAYLOAD #####");
try {
var updatedFields = documentChange.updateDescription.updatedFields;
Object.keys(updatedFields).forEach( function (key1, index1, _array1) {
var value1 = updatedFields[key1];
console.log(key1 + ": " + value1);
Object.keys(value1).forEach( function (key2, index2, _array2) {
var value2 = value1[key2];
console.log(key2 + ":" + value2);
});
});
}
catch(e) {
}
}
Here, I am parsing the response of the update peeling apart the updateDescription and looking specifically at the updatedFields.
For example, lets say I push a new object to my array...
db.user_relations.updateOne({ userId: "6fcfd7b3847dd9999430f1ad" }, { $push: { arrayOfFriends: { userId: 1, lastMessage: "Message 3" } } }
)
Example of output:
##### FULL PAYLOAD #####
{
_id: {
_data: '82612BD118000000012B022C0100296E5A10041B035F339E2B48B6A3A5E707D801316A46645F69640064612BD10D69B433970757F4EF0004'
},
operationType: 'update',
clusterTime: new Timestamp({ t: 1630261528, i: 1 }),
ns: { db: 'nodetest', coll: 'user_relations' },
documentKey: { _id: new ObjectId("612bd10d69b433970757f4ef") },
updateDescription: {
updatedFields: { 'arrayOfFriends.2': [Object] },
removedFields: [],
truncatedArrays: []
}
}
##### END FULL PAYLOAD #####
arrayOfFriends.2: [object Object]
userId:1
lastMessage:Message 3
To catch updates I needed to modify app.js client connect method - specifically the pipeline definition...
client.connect(function(err) {
const db = client.db("nodetest");
const collection = db.collection(collectionName);
const pipeline = [
{
"$match": {
"operationType": "update"
}
}
];
const stream = collection.watch(pipeline);
const changeStreamOptions = { fullDocument: "updateLookup" };
stream.on("change", stream_OnChange, changeStreamOptions);
});

UpdateOne is not working with array of objects in mongoDB

I try to update an object of an array in mongoDB. When the variable listID is not defined, it'll just update the first object of the array (but that's not what I want).
My goal is to add the word IDs, update the last_practiced field and increment the number of the word count field by the number of wordIDs.
I tried it with aggregation as well, but I couldn't get it to work.
My current query
Words.prototype.updatePersonalWordLists = async function(userID, listData) {
const listID = listData.list_id
const wordIDs = listData.word_ids
const last_practiced = listData.last_practiced
const numberOfNewWords = listData.word_ids.length
const lists = await personalWordLists.updateOne(
{"user_id": userID, "lists.$.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced,
$inc: {"lists.$.wordCount": numberOfNewWords}
}
}
)
return lists
}
let userID = "609b974f6bd8dc6019d2f304"
let listData = {
list_id: "609d22188ea8aebac46f9dc3",
last_practiced: "03-04-2021 13:25:10",
word_ids: ["1", "2", "3", "4", "5"],
}
word.updatePersonalWordLists(userID, listData).then(res => console.log(res))
My scheme
const mongoose = require('mongoose');
const personalWordListsSchema = new mongoose.Schema({
user_id: {
type: String
},
lists: Array
});
module.exports = personalWordListsSchema
I hope someone can help me to figure this out. Thanks in advance.
Thanks to #joe's comment I was able to make it work.
I removed the $ from the filter portion and used the ´´´$inc´´´ outside of the ´´´$set´´´ portion.
Word IDs are pushed only if they are unique.
Date of last practice can be updated.
Word count increments too.
const list = await personalWordLists.updateOne(
{"user_id": userID, "lists.list_id": listID },
{
$addToSet: { "lists.$.practiced_words": { $each: wordIDs}},
$set: {
"lists.$.last_practiced": last_practiced
},
$inc: {"lists.$.wordCount": numberOfNewWords}
}
)
return list

Mongodb: how to update a field only if the value I want to upload is not null?

I have a field named "profilePicture" that can have a string or a null value. When editing his profile, a user may or may not upload a new profile image. This mean that I can either receive a string or a null value.
How to update profilePicture only if the new value is not null (so I can keep the existing string )?
Here is the code:
const newProfilePicture = null // can be null or "string"
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
Thanks!
Try this
const updateObj = {};
// other properties added to object
// e.g.
// updateObj.name = "DoneDeal0";
if (newProfilePicture !== null) {
updateObj.profilePicture = "https://profilepicture.etc/picture.jpg";
}
const user = await User.findByIdAndUpdate(
{ userId:"someID" },
{ $set: updateObj },
{ new: true }
);
Use conditional logic to build an object prior to your mongo query.
I would do this using application logic. You can check beforehand if newProfilePicture is null, and if not perform the update. Something like this:
const newProfilePicture = null;
if (newProfilePicture){
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
}
If you need to return user regardless of whether an update actually occurs, you can just add an else branch and do a normal findById:
const newProfilePicture = null;
if (newProfilePicture){
const user = await User.findByIdAndUpdate(
userId,
[{$set: { profilePicture: newProfilePicture }}]
{ new: true }
);
} else {
const user = await User.findById(userId);
}

How to pass element index in array to mongoDB query?

Building cart on website and when product is added i want to first check if it is already in cart, if yes increment quantity by 1, if not add it. Cart is an array of objects and i want to pass index of object that contains added product to increment function but can't figure out how to do so.
async function add(product, userId) {
const user = await User.findById(userId);
const product = isProductInCart(product, user.cart); // returns true and index in cart if found
if (product.found === true) {
await User.findOneAndUpdate(
{ _id: userId },
{ $inc: { cart[product.index].quantity : 1 }} // not working
);
} else {
await User.findOneAndUpdate({ _id: userId }, { $push: { cart: product } });
}
}
function isProductInCart(product, cart) {
let productFound = { found: false, index: -1 };
for (let i = 0; i < cart.length; i++)
if (cart[i].name === product.name) {
productFound.found = true;
productFound.index = i;
break;
}
return productFound;
}
It looks like your code can be simplified if you consider using the $ positional operator:
let userWithCart = User.findOneAndUpdate(
{ _id: user, 'cart.name': product.name },
{ $inc: { 'cart.$.quantity' : 1 }}
)
if(!userWithCart ){
await User.findOneAndUpdate({ _id: userId }, { $push: { cart: product } });
}
First findOneAndUpdate will return no value when there's no corresponding cart.name (and you need to $push it). Otherwise MongoDB will automatically match the cart you want to update based on cart.name condition and increment the quantity subfield.
EDIT:
If you still need to proceed the way you've started you just need to evaluate the path in JavaScript:
{ $inc: { [`cart.${product.index}.quantity`] : 1 }}

Building MongoDB query with conditions

I need to build a MongoDB query by pushing a new language if it does not exist in the array already. But if it exists I get an error this '$push' is empty. It is correct.
My question is how to build the query adding $push only when it is necessary?
let pushNewLanguage = {};
if (!profile.languages || (profile.languages && !profile.languages.find(l => l === languageId))) {
pushNewLanguage = { languages: languageId };
}
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
{
$inc: { countPublishedPoems: 1 },
$push: pushNewLanguage
}
);
Remove the conditional logic and use $addtoSet instead of $push.
$addToSet will only add the item if it doesn’t exist already.
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
{
$inc: { countPublishedPoems: 1 },
$addToSet: { languages: languageId }
}
);
Since you are writing Javascript, you can create a "base" update object, and then add the $push property if you need:
const update = {
$inc: { countPublishedPoems: 1 }
}
if (!profile.languages || (profile.languages && !profile.languages.find(l => l === languageId))) {
update["$push"] = { languages: languageId };
}
const profileUpdate = await
Profiles.rawCollection().update(
{ userId: this.userId },
update
);