I'm Implementing Keyset pagination in a nestjs and mongodb project. And I'm trying to customize the original pagination solution. I want to make a condition if the startId document is the first document present in the collection.
This is the Code I'm trying. AND THE ISSUE WITH THIS CODE IS THAT IT RETURNS ALL THE DOCUMENTS WHATEVER THE ID YOU GIVE IN THE QUERY. I KNOW MY LOGIC COULD BE WRONG BUT ONE THING ABOUT I'M SURE AND THAT IS I'M WRITING THE SYNTAX, IS WRONG, AS THIS IS MY FIRST TIME EXPERIECE WITH MONGO QUERIES
async findAll( page?: number, documentsToSkip = 0, limitOfDocuments?: number,
startId?: string,
): Promise<userDocument[]> {
return await this.userModel
.find({
$cond: {
if: { $first: { _id: startId } },
then: { $gte: startId },
else: { $gt: startId },
},
})
.skip(documentsToSkip)
.limit(limitOfDocuments);
}
Explanation of the above code. For example I'm dealing with Users
1- If the startId(passed in query) is equal to the _id of the first document present in the user collection then the below then should be executed
then: { $gte: startId },
2- If the startId(passed in query) is not equal to the _id of the first document present in the user collection then the below else should be executed. Lets's say the pagination limit is set to 10 documents per page. And If I'm providing the id of the 11th document then else should be executed
else: { $gt: startId },
REASON FOR All THIS
The keyset solution present on the internet uses this
_id: { $gt: startId } and with this the document of the startedId is automatically skipped. SO I'M TRYING TO DO IF THE PAGINATION STARTS FROM THE FIRST DOCUMENT THEN THE FIRST DOCUMENT ITSELF SHOULD BE PRESENT AND VISIBLE. BUT THEN IF USER MOVES TO THE SECOND PAGE OF THE PAGINATION THE LAST DOCUMENT OF THE FIRST PAGE SHOULD NOT BE VISIBLE AT THE SECOND PAGE, AS THE LAST DOCUMENT ID BECOMES THE STARTING ID FOR THE SECOND PAGE DOCUMENTS
I've made the solution for my particular scenario. The issue was I hade to customize original pagination according to the first document present in the collection. But $first wasn't working for me. AS $first returns the first element of an Array and not the first document itself.
SOLUTION
1- To find the first document use findOne() method without any parameter. And it will simply return the first document of the collection.
let firstDocument: userDocument = await this.userModel.findOne();
2-For simple customization of the Pagination
if (startId == firstDocument._id) {
return await this.userModel
.find({
_id: { $gte: startId },
})
.skip(documentsToSkip)
.limit(limitOfDocuments);
} else {
return await this.userModel
.find({
_id: { $gt: startId },
})
.skip(documentsToSkip)
.limit(limitOfDocuments);
}
3- I had to make other changes as well so my original function now look different In case If someone want to know
async findAll(
page?: number,
limitOfDocuments?: number,
startId?: string,
): Promise<PaginatedUsersDto> {
let firstDocument: userDocument = await this.userModel.findOne();
let count = await this.userModel.count();
let totalPages = (count / limitOfDocuments).toFixed();
console.log('First Doucment in User Colelction', firstDocument);
if (startId == firstDocument._id) {
this.paginatedUsers = await this.userModel
.find({
_id: { $gte: startId },
})
.sort({ _id: 1 })
.skip((page - 1) * limitOfDocuments)
.limit(limitOfDocuments);
} else {
this.paginatedUsers = await this.userModel
.find({
_id: { $gt: startId },
})
.skip((page - 1) * limitOfDocuments)
.limit(limitOfDocuments);
}
let paginatedObj = {
paginatedUsers: this.paginatedUsers,
totalPages: totalPages,
count: count,
};
return paginatedObj;
}
I need to push a new document into an array and use his _id.
Right now im using findOneAndUpdate, i know i can use { new: true } but i only need the created document and not the parent document.
this is my code so far
User.findOneAndUpdate(
{
'posts._id': req.params.id
},
{
$push: {
'posts.$.comments': {
from: {
_id: req.user._id,
username: req.user.username,
},
body: req.body.commentBody
}
}
},
err => {
if (err) console.log(err)
// Do something with ObjectId
}
);
Thanks in advance.
findOneAndUpdate callback gives you the updated document.
You have the Post's _id. You can use that to find the post to which you are adding the comment to. As you are pushing the comment, it will be the last element in the post array.
var mypost = doc.posts.find(function(post){
return post._id.toString()==req.params.id
});
console.log(mypost.comments[mypost.comments.length]._id)
Is it possible to return a document after updating it?
my current code looks something like this:
module.exports.updateEmail = function(id, data, callback) {
User.findByIdAndUpdate( id, {
$set: { "email": email }
}, callback);
}
how would I pull the User document and pass it back?
add {new : true} update option, to return the updated document, the callback will have the updated document you can return it
module.exports.updateEmail = function(id, data, callback) {
User.findByIdAndUpdate( id,
{$set: { "email": email }},
{new : true}, // to return updated document
callback
);
}
Is there a way to update values in an object?
{
_id: 1,
name: 'John Smith',
items: [{
id: 1,
name: 'item 1',
value: 'one'
},{
id: 2,
name: 'item 2',
value: 'two'
}]
}
Lets say I want to update the name and value items for item where id = 2;
I have tried the following w/ mongoose:
var update = {name: 'updated item2', value: 'two updated'};
Person.update({'items.id': 2}, {'$set': {'items.$': update}}, function(err) { ...
Problem with this approach is that it updates/sets the entire object, therefore in this case I lose the id field.
Is there a better way in mongoose to set certain values in an array but leave other values alone?
I have also queried for just the Person:
Person.find({...}, function(err, person) {
person.items ..... // I might be able to search through all the items here and find item with id 2 then update the values I want and call person.save().
});
You're close; you should use dot notation in your use of the $ update operator to do that:
Person.update({'items.id': 2}, {'$set': {
'items.$.name': 'updated item2',
'items.$.value': 'two updated'
}}, function(err) { ...
model.update(
{ _id: 1, "items.id": "2" },
{
$set: {
"items.$.name": "yourValue",
"items.$.value": "yourvalue",
}
}
)
MongoDB Document
There is a mongoose way for doing it.
const itemId = 2;
const query = {
item._id: itemId
};
Person.findOne(query).then(doc => {
item = doc.items.id(itemId );
item["name"] = "new name";
item["value"] = "new value";
doc.save();
//sent respnse to client
}).catch(err => {
console.log('Oh! Dark')
});
There is one thing to remember, when you are searching the object in array on the basis of more than one condition then use $elemMatch
Person.update(
{
_id: 5,
grades: { $elemMatch: { grade: { $lte: 90 }, mean: { $gt: 80 } } }
},
{ $set: { "grades.$.std" : 6 } }
)
here is the docs
For each document, the update operator $set can set multiple values, so rather than replacing the entire object in the items array, you can set the name and value fields of the object individually.
{'$set': {'items.$.name': update.name , 'items.$.value': update.value}}
Below is an example of how to update the value in the array of objects more dynamically.
Person.findOneAndUpdate({_id: id},
{
"$set": {[`items.$[outer].${propertyName}`]: value}
},
{
"arrayFilters": [{ "outer.id": itemId }]
},
function(err, response) {
...
})
Note that by doing it that way, you would be able to update even deeper levels of the nested array by adding additional arrayFilters and positional operator like so:
"$set": {[`items.$[outer].innerItems.$[inner].${propertyName}`]: value}
"arrayFilters":[{ "outer.id": itemId },{ "inner.id": innerItemId }]
More usage can be found in the official docs.
cleaner solution using findOneAndUpdate
await Person.findOneAndUpdate(
{ _id: id, 'items.id': 2 },
{
$set: {
'items.$.name': 'updated item2',
'items.$.value': 'two updated',
}
},
);
In Mongoose, we can update array value using $set inside dot(.) notation to specific value in following way
db.collection.update({"_id": args._id, "viewData._id": widgetId}, {$set: {"viewData.$.widgetData": widgetDoc.widgetData}})
Having tried other solutions which worked fine, but the pitfall of their answers is that only fields already existing would update adding upsert to it would do nothing, so I came up with this.
Person.update({'items.id': 2}, {$set: {
'items': { "item1", "item2", "item3", "item4" } }, {upsert:
true })
I had similar issues. Here is the cleanest way to do it.
const personQuery = {
_id: 1
}
const itemID = 2;
Person.findOne(personQuery).then(item => {
const audioIndex = item.items.map(item => item.id).indexOf(itemID);
item.items[audioIndex].name = 'Name value';
item.save();
});
Found this solution using dot-object and it helped me.
import dot from "dot-object";
const user = await User.findByIdAndUpdate(id, { ...dot.dot(req.body) });
I needed to update an array element with dynamic key-value pairs.
By mapping the update object to new keys containing the $ update operator, I am no longer bound to know the updated keys of the array element and instead assemble a new update object on the fly.
update = {
name: "Andy",
newKey: "new value"
}
new_update = Object.fromEntries(
Object.entries(update).map(
([k, v], i) => ["my_array.$." + k, v]
)
)
console.log({
"$set": new_update
})
In mongoose we can update, like simple array
user.updateInfoByIndex(0,"test")
User.methods.updateInfoByIndex = function(index, info) ={
this.arrayField[index]=info
this.save()
}
update(
{_id: 1, 'items.id': 2},
{'$set': {'items.$[]': update}},
{new: true})
Here is the doc about $[]: https://docs.mongodb.com/manual/reference/operator/update/positional-all/#up.S[]
The mongoose schema looks like:
var followSchema = new Schema({
userId: {type: String, required: true},
following : []
});
The Objective is to look for a user using the ID field and then add items to the following array.
The below method does not work:
var myRecord = FollowModel.findOneAndUpdate({userId: req.user.Id}, {upsert: true});
myRecord.exec(function(err, result) {
result.following.push('Test');
res.status(200).send('New Item Added to Follow List');
});
How is this to be done ?
It does work, you just forgot to include the { "new": true } option, as well as a valid update statment:
FollowModel.findOneAndUpdate(
{ "userId": req.user.Id},
{ "$setOnInsert": {
}},
{ "upsert": true, "new": true },
function(err,result) {
if (err) throw err;
console.log(result);
}
);
The blank $setOnInsert here will no nothing in this case, but otherwise you would put something in there that you wanted created "on insert". This would be applied along with any fields in the "query" portion of the statement.
Other standard update operators can also apply, but generally in the case where something "is" matched that you want to update.
But it's the { "new": true } here that lets the command know to return the "modified or created" document, rather than the "original or non existing" document, which is the default without that option.