How to modify a child entry in a mongoDB - arrays

I've got a collection of forum posts, looking (roughly) like
{
"author": author,
"date": new Date(),
"content": req.body.content,
"comments":{
"deleted": 0,
"author": author,
"date": new Date(),
"content": req.body.content
}
}
if a user adds a comment, it is simply $pushed on to the current comments.
Now i want a possibility to delete one specific comment.
I inserted the "deleted" flag to review the comments as admin, rather than just completely deleting them.
but the problem is: how do i tell mongoDB which exact comment i want to delete?
I have just now added an "commentID" field, but i can't figure out how to auto-increment it (the documentation is really strange about this),
after having tried a lot of stuff like "comments.toDeleteId" or "comments[toDeleteId]" in the query.
TLDR: how can i query post "34563456354", comment "5", $set "deleted" to "1", where the comments are an array themselves?
thanks
EDIT
this is the console.dir of a post:
{_id: //etc etc
op: 'Anonymous',
postContent: 'testtest',
createdAt: //etc,
comments:
[ { deleted: 0,
posted: //etc,
author: 'Anonymous',
comment: 'test' },
{ deleted: 0,
posted: //etc,
author: 'Anonymous',
comment: 'testtesttest' } ]

Use $elemMatch in update as below :
db.collectioName.update({
"_id": ObjectId("55ba6729eb5f4a21914568df"),
"comments": {
"$elemMatch": {
"comment": "testtesttest"
}
}
}, {
"$set": {
"comments.$.deleted": 1
}
})
Or If you want to increment the deleted then use $inc as :
db.collectionName.update({
"_id": ObjectId("55ba6729eb5f4a21914568df"),
"comments": {
"$elemMatch": {
"comment": "testtesttest"
}
}
}, {
"$inc": {
"comments.$.deleted": 1
}
})
And If you want to push new object in comment then use $push in update as :
db.collectionName.update({
"_id": ObjectId("55ba6729eb5f4a21914568df")
}, {
"$push": {
"comments": {
"deleted": 1,
"posted": new Date(),
"author": "ABC",
"comment": "tests"
}
}
})

i have actually found a way to do this, even though i had searched before and did not find that thread.
the way to do it is actually quite simple, yet hard to grasp at first.
var selector = {};
var operator = {};
selector['comments.' + commentId + '.deleted'] = 1;
operator['$set'] = selector;
this gave me a
{'$set': {'comments.2.deleted': 1 } }
which i could easily insert into the query where
{_id: thisId,
operator,
cb)
(credits to https://stackoverflow.com/a/19115944/4547337 )

Related

Mongoose find all documents that have a string in an array

I've a question:
How i can find all documents that have a string in an array using mongoose?
For example, my document:
<Model>.findMany(/* code that i need */).exec() // return all documents that have an array called "tags" that includes tag "test"
{
"_id": {
"$oid": "61b129b7dd0906ad4a2efb74"
},
"id": "843104500713127946",
"description": "Server di prova",
"tags": [
"developers",
"programming",
"chatting",
"ita"
],
"shortDescription": "Siamo un server nato per chattare e aiutare programmatori su Discord!",
"invite": "https://discord.gg/NdqUqHBxz9",
"__v": 0
}
For example, if I need to get all documents with ita tag, I need to get this document.
If the document doesn't have ita tag in array tag, I don't need it and the code will not return it.
Thanks in advance and sorry for bad english.
Actually you can just request tags to be test since mongoose looks for every tags entry
so this:
await Test.insertMany([{ tags: ["test"] }, { tags: ["Notest"] }])
let res = await Test.find({ "tags": "test" })
console.log(res)
}
returns that:
[
{
_id: new ObjectId("61b8af02c3effad21a5d7187"),
tags: [ 'test' ],
__v: 0
}
]
2 more neat things to know:
This works no matter how many entries tags has, as long test is one of ethem
This also enables you to change the entrie containing the "test" by using positional $ operator, so something like {$set: "tags.$": "smthNew"} will change all the test entries
Example for 2nd:
let res = await Test.updateMany({ "tags": "test" }, { $set: { "tags.$": "new" } }, { new: true })

MongoDB - Update multiple subdocuments in array in multiple documents

I'm making a node.js website. I have a posts collection in which comments for posts are stored in an array with the comment author's details as nested object.
This is new post's schema:
{
"text": text,
"image": image,
"video": video,
"type": type,
"createdAt": createdAt,
"reactions": [],
"comments": [],
"shares": [],
"user": {
"_id": user._id,
"username": user.username
}
}
This is new comment being pushed to its post:
$push: {
"comments": {
"_id": commentId,
"user": {
"_id": user._id,
"type": type,
"name": user.name,
"profileImage": user.photo,
},
"comment": comment,
"createdAt": createdAt,
"replies": []
}
}
To avoid storing comments in another collection and doing complex multiple lookups(I'm doing 1 lookup to get post author details but couldn't add another to make it work for comments) to consolidate the newsfeed I decided to save comments and their author's details embedded in the posts.
Now when user profile picture is updated all the comments have to be updated to show the new picture.
I included this updateMany query along with the photo updation route in server.js file:
database.collection("posts").updateMany({
"comments.user._id": user._id,
"comments.user.type": "friend"
}, {
$set: {
"comments.$.user.profileImage": photo
}
});
The problem here is that this updates only the first matching comment in all posts.
I need to update all matching comments in all posts.
I'm actually just learning by doing this following youtube videos, so please help me.
You need to use arrayFilters I think.
If I've understand well your question this example should be similar to your DB.
The query is this:
db.collection.update({
"comments.user._id": 1,
"comments.user.type": "friend"
},
{
"$set": {
"comments.$[element].user.profileImage": "new"
}
},
{
"arrayFilters": [
{
"$and": [
{
"element.user._id": 1
},
{
"element.user.type": "friend"
}
]
}
],
"multi": true
})
First part is the same, and almost the second. You have to add element position into the array that is defined in the next step.
Using arrayFilters, you look for those whose match the comaprsion into $and. And only those ones will be updated.
Note that using updateMany() method, is not neccesary using {multi: true}

How to aggregate a MongoDB query to remove the usage of a for loop?

I am new to MongoDB and am working with it on NodeJS code.
As you can see the below code, I am running a for loop through my books collection to figure out the latest version of the query_book.
I know that this isn't efficient, and want to understand how can an aggregation function be written for it in MongoDB.
Current code:
let result= {};
_.forEach(books, function(query_book)
{
if(!result[query_book.book_id])
{
result[query_book.book_id] = query_book
}
else if(result[query_book.book_id].book_version <
query_book.book_version)
{
result[query_book.book_id] = query_book
}
Data Object for books:
[
{
"book_id": "ab12nld”,
"book_version": "0”,
"author": “Sam”,
“name”: “Sample Book”,
“comments”: “Done”
},
{
"book_id": "ab12nld”,
"book_version": "1",
"author": "Martin",
"name": "Sample Book",
“comments”: “In Progress”
},
{
"book_id": "ab12nld”,
"book_version": "2",
"author": "Roy",
"name": "Sample Book",
“comments”: “To-Do”
}
]
[
{
"book_id": "bcj123n”,
"book_version": "0”,
"author": “Don”,
“name”: “Another Book”,
“comments”: “Done”
},
{
"book_id": "bcj123n”,
"book_version": "1",
"author": "Ray",
"name": "Another Book",
“comments”: “In Progress”
},
{
"book_id": "bcj123n”,
"book_version": "2",
"author": "Max",
"name": "Another Book",
“comments”: “To-Do”
}
]
In this case, I want to fetch the object having the maximum value of book_version for my input book_id which is ab12nld:
{
"book_id": "ab12nld”,
"book_version": "2",
"author": "Roy",
"name": "Sample Book",
“comments”: “To-Do”
}
If using Node.js Mongo driver (replace [bookId] with your input)
db.collection('books')
.findOne({ book_id: [bookId] }, { sort: [['book_version', -1]] })
Or, if using Mongoose,
Book.findOne({ book_id: [bookId] }).sort({ book_version: -1 })
db.books.aggregate([{ "$sort": { "book_version": -1 } },{"$limit":1}])
I understood that you want to retrieve a document having highest value of a field.
Here, the document having the highest value in the version field. The simplest way of doing this is to sort in reverse order and get the first document.
You can also use the aggregate method.
Try the following snippet. I think it will help.
db.books.find().sort({"book_version":-1}).limit(1);

MongoDB update nested array elements

I have the following structure:
{
id: "1",
invoices: [{ id: "1", balance: 1},{ id: "2", balance: 1}]
},
{
id: "2",
invoices: [{ id: "3", balance: 1},{ id: "4", balance: 1}]
}
I'm getting a list of invoices IDs that i shouldn't update, the rest i need to update the balance to 0.
I'm pretty new to MongoDB and managing to find a way to do it.
Let say you want to update all invoices of id 1 except invoice.id 2 try this one:
db.collection.update(
{ id: "1", "invoices.id": {$ne: 2} },
{
$set: {
"invoices.$[]": { balance: 0 }
}
}
)
First of all, you forgot the quotes around the field names. Your documents should be like this:
{
"id": "1",
"invoices": [{
"id": "1",
"balance": 1
}, {
"id": "2",
"balance": 1
}]
}
I have limited experience with MongoDB, as I learnt it this semester at University. However, here is my solution:
db.collection.update(
{ id: "1" },
{
$set: {
"invoices.0": { id: "1", balance: 0 }
}
}
)
What does this solution do?
It takes the document with id 1. That is your first document.
The $set operator replaces the value of a field with the specified value. (straight out from the MongoDB manual - MongoDB Manual - $set operator).
"invoices.0" takes the first invoice from the invoices array and then it updates the balance to 100.
Replace the word collection from db.collection with your collection name.
Try and see if it works. If not, I'd like someone with more experience to correct me.
LE: Now it works, try and see.

How to update array elements in MongoDB instead of creating new one

I have problem with my code. I want to update document, which looks like this:
{
"_id": {
"$oid": "5bf2ad5a0d46b81798232cf9"
},
"manufacturer": "VW",
"model": "Golf ",
"VIN": "WVWZZZ1J212566691",
"doors": 3,
"class": "compact",
"reservations": [
{
"_id": {
"$oid": "5bf2ad5a0d46b81798232cfa"
},
"pick_up_date": {
"$date": "2014-10-13T09:13:00.000Z"
},
"drop_off_date": {
"$date": "2014-10-14T09:13:00.000Z"
},
"user_id": {
"$oid": "5bec00bdfb6fc005dcd5423b"
}
}
],
"createdAt": {
"$date": "2018-11-19T12:32:26.665Z"
},
"updatedAt": {
"$date": "2018-11-19T12:32:26.665Z"
},
"__v": 0
}
I want to add new reservation to array Reservations. My function:
const createReservationForCarId = ({ body , params }, res, next) =>
Carmodel.findById(params.id)
.then(notFound(res))
.then((carmodel) => carmodel ? Object.assign(carmodel, body).save() : null)
.then((carmodel) => carmodel ? carmodel.view(true) : null)
.then(success(res))
.catch(next)
But when I'm trying to update through:
router.put('/:id/reservation',
createReservationForCarId)
Body:
{
"reservations":[
{
"pick_up_date" : "October 13, 2017 11:13:00",
"drop_off_date": "October 14, 2017 11:13:00",
"user_id":"5bec00bdfb6fc005dcd5423b"
}
]
}
Mongo instead of update my old document is creating new one with only one reservation gave in above body.
What should I do to only update existing document, not creating new one?
I suggest you use $push operator of Mongoose which will append your object to the existing array and it will not create a new one.
Check out this link:-
https://github.com/Automattic/mongoose/issues/4338
You can try the findAndModify instead of findById in your code. Check out the syntax here
I haven't used mongoose but they have documentation for array manipulation.
See here:
https://mongoosejs.com/docs/api.html#mongoosearray_MongooseArray-push
Also here is the related mongo doc for array manipulation:
https://docs.mongodb.com/manual/reference/operator/update/push/

Resources