findAndModify MongoDB insert element at beginning of array - node.js

Update:
Problem here was that the native MongoDB driver needed the objectID in a different format than Mongoose. Instead of {_id: story_id} I needed to do, {_id: new mongoose.Types.ObjectId(story_id)}. The reason it returned just the two fields was that it was creating a new document with {_id: story_id} rather than updating the document which was {_id: {$oid: story_id}}. However, my original reason for doing this with the native driver vs. mongoose turned out to not work. Even the native driver does not accept positive $slice values. So I'm still looking for a way to insert an object to the beginning of an array using Mongoose or the native driver accessed through Mongoose (which does not support $position or positive $slice values).
When I run the query below, the returned results only include _id, messages and participants. I would like to get the full story record back rather than just the updated fields. Is there a way to do this? Based on Node MongoDB Native driver documentation (http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#findandmodify) the "fields" parameter from the mongo console is not implemented in the findAndModify driver. I want to avoid having to do a second query to get the just updated record.
Story.collection.findAndModify(
{"_id": story_id},
{"_id": "asc"},
{
"$push": {
"messages": {
"$each": [message],
"$sort": { "_id": -1},
"$slice": 500
},
},
"$addToSet": {"participants": user_id},
},
{new: true},
{fields: {"feeling":1, "description":1, "image_height":1, "image_width":1, "storyteller":1, "image_url":1, "participants":1}}, // -> this line is ignored by MongoDB native driver
{upsert: true}
)

You can insert elements at the beginning of an array field with a $push that uses the $each and $position modifiers (which is supported by Mongoose):
Story.findByIdAndUpdate(story_id, {
$push: {
messages: {
$each: [message],
$position: 0
},
},
$addToSet: {participants: user_id},
}, {
new: true,
upsert: true
},
function (err, newDoc) { ... }
);

Related

MongoDB Update & Aggregate Problem: Skipping over aggregation

I have two tables, one for messages to be logged and one that is for each member of the group. When a message is upvoted, I want to add an entry for the message and push each reaction on that message to an array.
Every time a reaction is added I want to update the member table to reflect the sum of all of the reaction.value fields.
This is the code I have written to do so. When this runs from a sandbox I made in VisualStudio using a MongoDB add on it runs fine, however when ran using my app, only the message document is added and without any error it appears to skip the aggregation to the member document.
Here is the code I am using to insert the documents to the database and to aggregate once the document is inserted:
await mongodb.collection("Messages").updateOne({ _id: reaction.message.id.toString()},
{
$set:{
authorid: reaction.message.author.id,
author: reaction.message.author.username
},
$push: {
reactions: {
reauth: reAuth,
reaction: reaction.emoji.name,
removed: false,
value: actualKarmaDB,
}
}
}, {safe: true, "upsert": true})
await mongodb.collection("Messages").aggregate([
{
$match: {
_id: reaction.message.id
}
},
{
$project: {
_id: "$authorid",
username: "$author",
messageKarma: {$sum: "$reactions.value"},
}
},
{ $merge: {
into: "Members",
on: "_id",
whenMatched: "replace",
whenNotMatched: "insert"
}
}])
Also here is a look at what the insertion into “Messages” looks like:
In this case the answer was due to mongoose not supporting merge for aggregation. Had to use $out.
Additionally this is a known issue with Mongoose Node.js, see here:
Mongodb node.js $out with aggregation only working if calling toArray()

How can I mix a populated ObjectId with a string

Actually, in the database I got a job that I request with a GET route:
So when I populate candidates I got this response format :
My problem here is I don't need that "id" object, I just need a "selected_candidates" array with users inside as objects. Actually it's an object, in another object that is in an Array.
Here the code from my controller (the populate is in the jobsService):
If I change the data format of the job like that way:
...It is working great (with a path: "candidates_selected") like expected BUT I don't have that "status" string (Normal because I don't have it anymore in the DataBase. Because of ObjectId):
I would like a solution to have them both, but maybe it's the limit of noSQL?
A solution without populate but with a Loop (I don't think it's a good idea):
I think there is no convenience way to achieve it. However you may try the aggregate framework from the native MongoDB driver.
Let your Mongoose schemas be ASchema and BSchema
const result = await ASchema.aggregate([
{$addFields: {org_doc: '$$ROOT'}}, // save original document to retrieve later
{$unwind: '$candidates_selected'},
{
$lookup: {
from: BSchema.collection.name,
let: {
selected_id: '$candidates_selected.id',
status: '$candidates_selected.status',
},
pipeline: [
{
$match: {$expr: {$eq: ['$$selected_id', '$_id']}}, // find candidate by id
},
{
$addFields: {status: '$$status'} // attach status
}
],
as: 'found_candidate'
}
},
{
$group: { // regroup the very first $unwind stage
_id: '$_id',
org_doc: {$first: '$org_doc'},
found_candidates: {
$push: {$arrayElemAt: ['$found_candidate', 0]} // result of $lookup is an array, concat them to reform the original array
}
}
},
{
$addFields: {'org_doc.candidates_selected': '$found_candidates'} // attach found_candidates to the saved original document
},
{
$replaceRoot: {newRoot: '$org_doc'} // recover the original document
}
])

Mongoose findByIdAndUpdate() with select option [duplicate]

Using Mongoose in Nodejs you can return some fields using find.
eg.
User.findOne({_id:'132324'}, {first_name:1, last_name:1}).exec...
but I can't seem to figure out how to return certain fields using findOneAndUpdate.
User.findOneAndUpdate({_id:'132324'}, {$set : {..bla bla}}, {first_name:1, last_name:1}).exec....
Has anyone achieved this before?
I can't find it in the documentation.
From the manual, the options argument needs a "fields" key in it since there are other details such as "upsert" and "new" where this applies. In your case you also want the "new" option:
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"fields": { "first_name":1, "last_name": 1 },
"new": true
}
).exec(...)
Alternately you may use .select()
User.select({ "first_name": 1, "last_name": 1 }).findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{ "new": true }
).exec(...)
Noting that without "new": true the document returned is in the state before the modification of the update was processed. Some times this is what you mean, but most of the time you really want the modified document.
It seems the syntax for findByIdAndUpdate has changed a little.
Its takes the form of Model.findByIdAndUpdate(query, update, options, (optional callback))
According to the docs, to specify which fields are returned you have to specify them in the options parameter. so, using the above example it would be:
User.findOneAndUpdate(
{id}, //query
{ $set: { "fieldToBeChanged": "update" } }, //update
{new:true, select: "fieldIWant anotherFieldIWant"}, //options
})
The new:true options is not necessary. If omitted mongoose defaults to returning the document before the updates were applied. Set to true and it will return the document after the updates have been made.
We can exclude any field while using mongoose update function findByIdAndUpdate with the help of select function,please have a look at the following code it will exclude password and __v from the Update response
User.findByIdAndUpdate({ _id: req.userData.UserId },req.body,{new: true}).select({Password: 0, __v: 0 })
.exec()
.then(result => {})
Try this:
User.findOneAndUpdate({_id:'132324'}, {update: '1235'}, {new: true}).select({ _id: 0 })
The select() will exclude the _id field from the result.
For MongoDb version 3.2+
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"projected": { "first_name":1, "last_name": 1 },
"returnNewDocument": true
}
)...
For MongoDB version: 4.0.2, mongoose version: 5.13.7:
userModel.findOneAndUpdate({_id:'132324'},{$set:
{"first_name":"user","last_name":"one"}},
{new:true,select:{first_name:1, last_name:1}},(err,data)=>{}
Use option {new: true} in findOneAndUpdate (default: false)

mongodb remove document if array count zero after $pull in a single query

I have a requirement where my comments schema looks like the following
{
"_id": 1,
"comments": [
{ "userId": "123", "comment": "nice" },
{ "userId": "124", "comment": "super"}
]
}
I would like to pull the elements based on the userId field.
I am doing the following query
comments.update({},{$pull:{comments:{userId:"123"}}})
My requirement is that if the array length became zero after the pull operator I need to remove the entire document for some reason.Is there a away to do this in a single query?
PS:I am using the mongodb driver.Not the mongoose
If I'm reading your question right, after the $pull, if the comments array is empty (zero length), then remove the document ({ _id: '', comments: [] }).
This should remove all documents where the comments array exists and is empty:
comments.remove({ comments: { $exists: true, $size: 0 } })
I had a similar requirement and used this (using mongoose though):
await Attributes.update({}, { $pull: { values: { id: { $in: valueIds } } } }, { multi: true })
await Attributes.remove({ values: { $exists: true, $size: 0 } })
Not sure if it's possible to do this in one operation or not.
You can use middlewares for this.
http://mongoosejs.com/docs/middleware.html
Write a pre/post update method in mongodb to check your condition.

Mongoose select fields to return from findOneAndUpdate

Using Mongoose in Nodejs you can return some fields using find.
eg.
User.findOne({_id:'132324'}, {first_name:1, last_name:1}).exec...
but I can't seem to figure out how to return certain fields using findOneAndUpdate.
User.findOneAndUpdate({_id:'132324'}, {$set : {..bla bla}}, {first_name:1, last_name:1}).exec....
Has anyone achieved this before?
I can't find it in the documentation.
From the manual, the options argument needs a "fields" key in it since there are other details such as "upsert" and "new" where this applies. In your case you also want the "new" option:
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"fields": { "first_name":1, "last_name": 1 },
"new": true
}
).exec(...)
Alternately you may use .select()
User.select({ "first_name": 1, "last_name": 1 }).findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{ "new": true }
).exec(...)
Noting that without "new": true the document returned is in the state before the modification of the update was processed. Some times this is what you mean, but most of the time you really want the modified document.
It seems the syntax for findByIdAndUpdate has changed a little.
Its takes the form of Model.findByIdAndUpdate(query, update, options, (optional callback))
According to the docs, to specify which fields are returned you have to specify them in the options parameter. so, using the above example it would be:
User.findOneAndUpdate(
{id}, //query
{ $set: { "fieldToBeChanged": "update" } }, //update
{new:true, select: "fieldIWant anotherFieldIWant"}, //options
})
The new:true options is not necessary. If omitted mongoose defaults to returning the document before the updates were applied. Set to true and it will return the document after the updates have been made.
We can exclude any field while using mongoose update function findByIdAndUpdate with the help of select function,please have a look at the following code it will exclude password and __v from the Update response
User.findByIdAndUpdate({ _id: req.userData.UserId },req.body,{new: true}).select({Password: 0, __v: 0 })
.exec()
.then(result => {})
Try this:
User.findOneAndUpdate({_id:'132324'}, {update: '1235'}, {new: true}).select({ _id: 0 })
The select() will exclude the _id field from the result.
For MongoDb version 3.2+
User.findOneAndUpdate(
{ "_id": "132324" },
{ "$set": { "hair_color": "yellow" } },
{
"projected": { "first_name":1, "last_name": 1 },
"returnNewDocument": true
}
)...
For MongoDB version: 4.0.2, mongoose version: 5.13.7:
userModel.findOneAndUpdate({_id:'132324'},{$set:
{"first_name":"user","last_name":"one"}},
{new:true,select:{first_name:1, last_name:1}},(err,data)=>{}
Use option {new: true} in findOneAndUpdate (default: false)

Resources