UpdateOne is not working with array of objects in mongoDB - 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

Related

mongoose update inside array object

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

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

Store array of string into the mongoDB collection

I am very new to MongoDB and mongoose I have a model name called agent
agent.model.js
const mongoose = require('mongoose')
const agentSchema = new mongoose.Schema({
agent: {
type: String,
required: true
}
})
const Agent = mongoose.model('Agent', agentSchema)
module.exports = Agent;
Now I have a array of string:
const agentName = ['john', 'alex', 'david'];
Now I want to store this array into the mongoDB as an individual agent.
like this:
[
{
"_id": "6000977d9b94f52960955066",
"agent": "john",
"__v": 0
},
{
"_id": "6000977d9b94f52960955067",
"agent": "alex",
"__v": 0
},
{
"_id": "6000977d9b94f52960955068",
"agent": "david",
"__v": 0
}
]
Note: Right now First I am converting my array of string into the array of object using loop like this:
agentName = agentName.map((e) => {return {agent: e}})
//output of above line of code
[ { agent: 'Alex Watson' },
{ agent: 'John Snow' },
{ agent: 'Rita Ora' } ]
Then I am saving the agentName.
But I am looking for some better approach, Like in which there is no need of converting the array of string into the array of object.
you must to use insertMany() function is used to insert multiple documents into a collection. It accepts an array of documents to insert into the collection. like following code, so have to create a array of abjects, that you created
note: in your question define const agentName next step assign result of map to the constant variable, so this is wrong
const agentName = ['john', 'alex', 'david'];
let arr = agentName.map((e) => {return {agent: e}})
Agent.insertMany(arr).then(function(){
console.log("Data inserted") // Success
}).catch(function(error){
console.log(error) // Failure
});

Mongoose populate method not populating all fields after updating document

I change the order of an array and then populate doesn't work. It still works on the documents that I did not try to update.
I have a seccion model with preguntas inside of it
SECCION MODEL
const Schema = mongoose.Schema;
const seccionSchema = new Schema({
titulo: { type: String },
preguntas: [
{
type: Schema.Types.ObjectId,
ref: 'Pregunta',
},
],
});
PREGUNTA MODEL
const Schema = mongoose.Schema;
const preguntaSchema = new Schema({
titulo: { type: String, required: true },
});
A "Seccion" document with 2 "preguntas" looks something like this:
{
"preguntas": [
"5fb0a48ccb68c44c227a0432",
"5fb0a48ccb68c44c227a0433"
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 3
}
What I want to do is change the order in the preguntas array, so that the _ids end up being [33, 32] instead of [32, 33] (last two digits of the _ids). I can update using ".save()" method or ".findOneAndUpdate()". Here is the code using "findOneAndUpdate":
router.put('/:id', async (req, res) => {
const seccionId = req.params.id;
try {
const seccion = await Seccion.findOneAndUpdate(
{ _id: seccionId },
{ preguntas: ['5fb0a48ccb68c44c227a0433', '5fb0a48ccb68c44c227a0432'] },
{ new: true }
);
res.json(seccion);
} catch (err) {
res.json({ message: err });
}
});
The result works (and the order is changed in mongo atlas):
{
"preguntas": [
"5fb0a48ccb68c44c227a0433",
"5fb0a48ccb68c44c227a0432"
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 4
}
But when I try to use the "populate()" method of mongoose, only one of the "preguntas ids" or none are populated (below only the "pregunta" with id ending in 32 was populated):
{
"preguntas": [
{
"_id": "5fb0a48ccb68c44c227a0432",
"titulo": "Pregunta 3",
"__v": 0
}
],
"_id": "5fb0a50fcb68c44c227a0436",
"titulo": "Seccion 2",
"__v": 4
}
So my guess is that the populate method is not working correctly, but it does work on the other "Seccion" documents where I have not tryed to change the order of the "preguntas" inside the array. Here is the code that uses the "populate" method:
router.get('/:id', async (req, res) => {
const seccionId = req.params.id;
try {
const seccion = await Seccion.findById(seccionId).populate('preguntas');
res.json(seccion);
} catch (err) {
res.json({ message: err });
}
});
It has been really hard to make the populate work, I really appreciate anyone's help
Are you sure that pregunta of id 5fb0a48ccb68c44c227a0433 exists in the preguntas collection?
The way populate works is that it does a second find operation on the preguntas collection with the ids in the array, if a document is not found, it will be omitted from the array.
The issue is probably that the one ending with 33 does not exist in the preguntas collection.

Mongodb - Get back object instead of array from find

db.users.find();
Will return me an array of users:
[{
_id: 123
name: bob
},{
_id: 456
name: tom
}]
I need to map users to another collection by the id, so I would like to get an object back from mongo where the keys are _id and values are the user doc.
i.e.
users = {
123: {_id: 123, name: bob},
456: {_id, 456, name:tom}
}
Then I can access users directly from that object without having to iterate an array to find specific users.
id = 123;
user = users[id];
You can't get an object like this one from mongodb, but it's quite easy to build it yourself:
db.users.find(function (err, docs) {
var users = {};
docs.forEach(function (doc) {
users[doc._id] = doc;
});
do_whatever_you_want_next(users);
});
Posting my solution in a more modern syntax:
const pushSubscriptions = await PushSubscription.find({ token: { $in: tokens } }).exec();
const userTokens = pushSubscriptions.reduce(
(result, ps) => {
result[ps.token] = ps;
return result;
},
{});