I am trying to remove keywords with Nodejs and Express from a mongoose document that looks somewhat like this:
{
name: "Instagram",
description: "Image sharing website",
keywords: [{name:"Image", value: 1}, {name:"sharing", value: 1}, {name:"website"}, {name:"Instagram", value:5}, {name:"application", value: 2}]
}
Here is the part of my update query which seems to the problem (it does not delete the keywords properly if there are many keywords, though it has worked a couple of times with few keywords):
Model.findOne({_id:req.body.id}, function(err,doc){
for(var i = 0; i < doc.keywords.length; i++){
if(doc.keywords[i].value == 1){
doc.keywords.splice(doc.keywords[i], 1); //does nothing
doc.save()
console.log(doc.keywords[i]) //Shows the correct keywords to be deleted.
}
};
})
Splice doesn't work on an array of objects. See Remove Object from Array using JavaScript for optional solutions to that. Otherwise I'd suggest a different method, why don't you just filter the keywords based on your needs like:
doc.keywords = doc.keywords.filter((key) => key.value === 1);
Related
I'm working on a project in nodejs using mongodb as my database. I'm trying to get rid of elements within my array that have dates before today. The problem that I'm having is that at most 5 elements are being deleted. I want all elements that meet this criteria to be deleted. Also, when I don't have user.possible.pull(items._id) const result = await user.save() all elements that meet this criteria are shown in my deletePossible array. However, when I do have user.possible.pull(items._id) const result = await user.save() at most 5 are being shown as well.
In my database, my User document looks like:
_id: '',
name: '',
possible: Array
0 Object
date: "Tues Jan 10 2023",
_id: "63c0b169b6fa12ac49874a13"
1 Object
date: "Wed Jan 11 2023",
_id: "63c0b172b6fa12ac49874a32"
...
My code:
const user = await User.findById(args.userId)
const deletePossible = [];
for (var items of user.possible) {
if (+new Date(items.date) < +new Date().setHours) {
deletePossible.push(items._id)
user.possible.pull(items._id)
const result = await user.save()
}
}
`
console.log(deletePossible)
I've tried a number of things such as:
for (var item of deletePossible) {
user.possible.pull(item)
const result = await user.save()
}
following deletePossible.push(items._id), and
const userInfo = await User.updateOne( { _id: args.userId}, {possible:{$pull:[...deletePossible] }} )
which removes all of the arrays from possible regardless of if it's contained within deletePossible and then adds a random _id. Nothing I have tried seems to work. Does anyone have any idea why this is happening and how to get this to work properly? I would really appreciate any help or advice. Thank you!
You can simply filter user.possible and save the updated User:
const user = await User.findById(args.userId);
if (!user) return;
// Change the condition based on your needs
user.possible = user.possible.filter(p => new Date(p.date) >= new Date());
await user.save();
The core of the issue appears to not be related to Mongo or Mongoose really, but is rather just a standard algorithmic logic problem.
Consider the following code, which iterates over an array, logs each element, and removes the third element when it arrives at it:
const array = [0, 1, 2, 3, 4];
for (const element of array) {
console.log(element);
if (element === 2) {
array.splice(2, 1); // remove the element at index 2 from the array
}
}
This code outputs:
0
1
2
4
Notice anything interesting? 3 has been skipped.
This happens because deleting an element from an array causes everything in front of it to move up a position. So if you're looking at 2 and you delete it, then 3 moves into 2's place, and 4 moves into 3's place. So then if you look at the next position, you're now looking at 4, not 3. The code never sees the 3.
This is why you should never change an array while iterating over it. A lot of languages won't even allow you to (if you're using iterators), they'll throw some sort of "underlying collection was modified during iteration" error. You can make it work if you know what you're doing (often just by iterating over the array backwards), but there are usually better solutions anyway, like using Array.prototype.filter().
One easy solution is to iterate over a copy of the array, so that when you do the delete, the array you're iterating over (the copy) isn't changed. That would look like this:
for (const item of [...user.possible]) {
if (/* some condition */) {
user.possible.pull(item._id);
}
}
Another problem with your code: +new Date().setHours will always evaluate to NaN since setHours is a function and converting a function to a number always results in NaN. I suspect this is just a typo you introduced while struggling with the original issue.
The suggestion to use filter() is even better.
I have 3 collections in my application.
Member - has a field called MemberMatches which is referencing the Matches collection.
Tournament - has a field called TournamentMatches which is referencing the Matches collection
Matches.
On the REACT front end on the click of a button, I am creating a bunch of matches using a for loop and making entries in the matches collection via node. At the time I want to enter the Object ID into the Member and Tournament collections.
Here is the code:-
for(i=0; i<playersList.length;){
player1Name = playersList[i]
for(j=0; j<playersList.length;){
if(j<=i){
j=i
j++
continue
}
player2Name = playersList[j]
var newMatch = new MatchRegister({
Player1Name: player1Name,
Player1GroupNumber: groupNumber,
Player2Name: player2Name,
Player2GroupNumber: groupNumber,
});newMatch.save(async function(err,data){
if(err){
console.log(err)
} else {
await Tournament.findOneAndUpdate({_id: tid}, {$push: {TournamentMatches: data._id}})
await Member.findOneAndUpdate({MemberName: data.Player1Name}, {$push: {MemberMatches: data._id}})
await Member.findOneAndUpdate({MemberName: data.Player2Name}, {$push: {MemberMatches: data._id}})
}
})
j++
}
i++
}
}
return
I am calling this function from the route.push. Saving the matches first and then 3 FindOneAndUpdate with the await keyword.
This entire code and functionality works. Upon execution I can clearly see that the data is being populated in all 3 collections correctly.
Question: Is this the right approach? Is there another Mongoose way to do this.
I'm developing a small NodeJS web app using Mongoose to access my MongoDB database. A simplified schema of my collection is given below:
var MySchema = mongoose.Schema({
content: { type: String },
location: {
lat: { type: Number },
lng: { type: Number },
},
modifierValue: { type: Number }
});
Unfortunately, I'm not able to sort the retrieved data from the server the way it is more convenient for me. I wish to sort my results according to their distance from a given position (location) but taking into account a modifier function with a modifierValue that is also considered as an input.
What I intend to do is written below. However, this sort of sort functionality seems to not exist.
MySchema.find({})
.sort( modifierFunction(location,this.location,this.modifierValue) )
.limit(20) // I only want the 20 "closest" documents
.exec(callback)
The mondifierFunction returns a Double.
So far, I've studied the possibility of using mongoose's $near function, but this doesn't seem to sort, not allow for a modifier function.
Since I'm fairly new to node.js and mongoose, I may be taking a completely wrong approach to my problem, so I'm open to complete redesigns of my programming logic.
Thank you in advance,
You might have found an answer to this already given the question date, but I'll answer anyway.
For more advanced sorting algorithms you can do the sorting in the exec callback. For example
MySchema.find({})
.limit(20)
.exec(function(err, instances) {
let sorted = mySort(instances); // Sorting here
// Boilerplate output that has nothing to do with the sorting.
let response = { };
if (err) {
response = handleError(err);
} else {
response.status = HttpStatus.OK;
response.message = sorted;
}
res.status(response.status).json(response.message);
})
mySort() has the found array from the query execution as input and the sorted array as output. It could for instance be something like this
function mySort (array) {
array.sort(function (a, b) {
let distanceA = Math.sqrt(a.location.lat**2 + a.location.lng**2);
let distanceB = Math.sqrt(b.location.lat**2 + b.location.lng**2);
if (distanceA < distanceB) {
return -1;
} else if (distanceA > distanceB) {
return 1;
} else {
return 0;
}
})
return array;
}
This sorting algorithm is just an illustration of how sorting could be done. You would of course have to write the proper algorithm yourself. Remember that the result of the query is an array that you can manipulate as you want. array.sort() is your friend. You can information about it here.
i have the following simplified Scheme:
var restsSchema = new Schema({
name: String
menu: [mongoose.Schema.Types.Mixed]
});
My document can look like:
{
name: "Sandwiches & More",
menu: [
{id:1,name:"Tona Sandwich",price: 10, soldCounter:0},
{id:2,name:"Salami Sandwich",price: 10, soldCounter:0},
{id:3,name:"Cheese Sandwich",price: 10, soldCounter:0}
]
}
The collection rests is indexed with:
db.rests.createIndex( { "menu.id": 1} , { unique: true })
Lets say i have this array of ids [1,3] and based on that i need to increment the soldCounter by 1 of menu items with ids=1 or 3.
What will be the must efficient way of doing so?
thanks for the helpers!
EDIT:
I have used the following solution:
db.model('rests').update({ _id: restid,'menu.id': {$in: ids}}, {$inc: {'menu.$.soldCounter': 1}}, {multi: true},function(err) {
if(err)
console.log("Error while updating sold counters: " + err.message);
});
where ids is an array of integers with ids of menu items.
restid is the id of the specific document we want to edit in the collection.
For some reason only the first id in the ids array is being updated.
There is a way of doing multiple updates, here it is:
Just make sure you have the indexes in the array you want to update.
var update = { $inc: {} };
for (var i = 0; i < indexes.length; ++i) {
update.$inc[`menu.${indexes[i]}.soldCounter`] = 1;
}
Rests.update({ _id: restid }, update, function(error) {
// ...
});
it seems not possible to update multiple subdocuments at once (see this answer). So a find & save seems to be the only solution.
Rest.findById(restId).then(function(rest){
var menus = rest.menu.filter(function(x){
return menuIds.indexOf(x.id) != -1;
});
for (var menu of menus){
menu.soldCounter++;
}
rest.save();
});
In the end it's only one find and one save requests.
I have a mongodb model called User, which has a mixed schema type variable called "inventory" (contains all the items a user contains). I would like to loop through all the users, and change the name of each item in their inventory. In particular, I would like to convert strings in the format of "10_alex_magician" or "3_maia_princess" to "alex_magician" and "maia_princess" respectively. The string conversion is relatively straightforward, and I'm using x.split('').slice(1).join('') to accomplish the conversion.
Where I'm having trouble is even when console.log shows that the conversion has been applied, it doesn't seem to be updating correctly to mongodb, yet no error message is being thrown. Does anyone know how to solve this?
Node.js function
//function to change old naming of items "10_alex_magician" to "alex_magician"
function modifyUser() {
User.find({}, function(err, results) {
_.map(results, function(result) {
var regex = /^\d+_[A-Za-z]+_[A-Za-z]+$/
for (var i = 0, len = result.inventory.length; i < len; i++) {
if(regex.test(result.inventory[i].itemName)) {
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.save(function(err, r) {
if(err) console.log(err);
//logging r shows that the text has been correctly updated
console.log(r)
});
}
}
})
})
}
Format of inventory variable
"inventory": [
{
"type": "sticker",
"numberOwned": 2,
"itemName": "1_alex_magician"
},
{
"type": "sticker",
"numberOwned": 1,
"itemName": "10_alex_scuba"
}
],
Mongoose only has automatic change detection for top-level properties and you are modifying a nested property, so mongoose doesn't know anything changed. Use markModified to tell mongoose you are mucking with inventory.
result.inventory[i].itemName = result.inventory[i].itemName.split('_').slice(1).join('_');
result.markModified('inventory');
result.save(....)
For efficiency, you may want to consider both .lean() and .stream() for this type of query and just do your updates with findByIdAndUpdate, passing just the updated inventory property.