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.
Related
I am trying to build a "number of visitors" collection in mongoDb using Node.JS backend of my website. The frontend sends the following info to Node.JS backend as JSON.
isUniqueVisitor - 1 if yes, 0 if no
country - standard country code - "JP", "IN", "UK", etc
My database looks like following
{
"today": 2019-06-07,
"uniqueVisitors": {
"count": 230,
"countries": {
"JP": 102,
"IN": 88,
"UK": 30
}
}
}
It works well if I use $inc with fixed values
Eg. $inc: {count: 1} // for string/integers keys
Eg. $inc: {"uniqueVisitors.count": 1} // inside quotes to access key of a JSON
Main issue:
I am not able to access a document name using variable.
Eg. $inc: {`uniqueVisitors.countries[${req.body.country}]`}
This creates an error as backticks can't be used for Mongo.
I tried with
Eg. $inc: {uniqueVisitors["countries"][req.body.country]}
But even this creates error.
I followed the web and found that mongo $set using variables can be realized by passing the required JSON directly to $set. Hence I resorted to code it the following way.
mongoClient.connect(mongoURL, async function (err, db) {
if (err) throw err;
console.log("Database connected");
// Identifying my document with today's date
var myQuery = {
date: getTodayDate()
};
// Defining the JSON to be passed to uniqueVisitors $inc
var uniqueVisitorsInc = {
"uniqueVisitors": {
"count": 0,
"countries": {}
}
};
// Populating the JSON to be passed to uniqueVisitors $inc => essentially asking to increase count by 1 and increase that country's count by 1
uniqueVisitorsInc["uniqueVisitors"]["count"] = 1;
uniqueVisitorsInc["uniqueVisitors"]["countries"][myData.country] = 1;
var newValues = {
$inc: uniqueVisitorsInc
};
await db.collection("visitorStats").update(myQuery, newValues, {upsert: true});
db.close();
});
The above method worked well on editor but threw the following runtime error:
$inc requires numerical values
Basically asking me to pass values to $inc in {var1: 1, var2: 5} pattern.
Please help me bypass this weird situation.
I know I can do a two step process where I read the values first, increment in variable and $set it in Mongo.
But does anyone know how to overcome this situation using $inc?
If this update were hardcoded to update "JP" only, it'd need to look like:
$inc: { "uniqueVisitors.country.JP": 1 }
So you were almost there with the backtick method but change the syntax a bit and keep the : 1 part like so:
$inc: { [`uniqueVisitors.country.${req.body.country}`]: 1 }
I want to get the size of the array in my DB so I can loop through it, so I created a field called Size in my DB. I want to store the value of this field in a variable so I know how many times I need to loop through the array. I am able to print out the whole document, but I cannot single out the Size value.
Here is my DB document:
_id:ObjectId("5c02492d1c9d440000498a9b")
Names:Array
Size:5
Now, this is my code that I am trying to extract the number 5 from the size field.
var cursor = db.collection('Room').find('Size');
cursor.forEach(function (err, num) {
if(err) {
return console.log(err);
}
console.log(num);
});
This is what console.log(num) prints:
{ _id: 5c02492d1c9d440000498a9b,
Names:
[ 'TjAa0wOe5k4',
'Sz_YPczxzZc',
'mM5_T-F1Yn4',
'En6TUJJWwww',
'5T_CqqjOPDc' ],
Size: 5 }
Any ideas/help?
For anyone who is wondering, I figured it out. It turns out that turning the result into an array and then calling numArr[0].Size works. Here is the code for anyone curious:
var length;
db.collection('Room').find({'Size': Object}).toArray((err, numArr) => {
if(err) {
return console.log(err);
}
length = numArr[0].Size
return length;
});
Update:
OP fixed it by converting the cursor to an array using .toArray() method and then referencing the property like numArr[0].Size.
You can access the Size property like this:
var size = num.Size;
Have you tried it this way?
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 am building an array with data and want to push that array to sub-document.
var pubArray = [];
var count = 5
for (i = 0; i < count; i++) {
pubArray.push({publicationName: req.body.publicationName[i], dateSent:req.body.dateSent[i]});
};
Students.findOne({studentNumber: filter}, function (err, student) {
student.publications.push({pubArray});
student.save();
});
If I use the {publicationName: req.body.publicationName[i], dateSent:req.body.dateSent[i]} inside the student.publications.push it works fine. If I try to push the array, nothing happens.
Note that the .push() method in mogoose works just like it's JavaScript equivalent in that it is "pushing" a single element onto the array, rather than a whole array. So you can either assign the whole array or just construct in the loop:
student.publications = pubArray;
or:
// Construct with .push in loop:
Students.findOne({ "studentNumber": filter },function(err,student) {
for ( var i = 0; i < count: i++ ) {
student.publications.push({
"publicationName": req.body.publicationName[i],
"dateSent": req.body.dateSent[i]
});
}
student.save(function(err) {
// Complete
});
});
But really you would be better off using an "atomic" operator of $push with $each in a direct update. This is then just one trip to the server, rather than two:
Students.update(
{ "studentNumber": filter },
{ "$push": { "publications": { "$each": pubArray } } },
function(err,numAffected) {
}
);
That is generally worlds better than the "find/modify/save" pattern, and not only in being more efficient, but it also avoids potential conflicts or overwriting data since the object and array is modified "in-place" in the database, with the state current to the time of modification.
Atomic operators should always be favoured for the performance benefits as well as lack of conflicts in modification.
The publications property of the student object is an Array. You can simply assign this property to the pubArray created earlier:
Students.findOne({studentNumber: filter}, function (err, student) {
student.publications = pubArray;
student.save();
});
Is it possible to $addToSet and determine which items were added to the set?
i.e. $addToSet tags to a post and return which ones were actually added
Not really, and not with a single statement. The closest you can get is with the findAndModify() method, and compare the orginal document form to the fields that you submitted in your $addToSet statement:
So considering an initial document:
{
"fields": [ "B", "C" ]
}
And then processing this code:
var setInfo = [ "A", "B" ];
var matched = [];
var doc = db.collection.findAndModify(
{ "_id": "myid" },
{
"$addToSet": { "fields": { "$each": setInfo } }
}
);
doc.fields.forEach(function(field) {
if ( setInfo.indexOf(field) != -1 ) {
matched.push(field);
}
});
return matched;
So that is a basic JavaScript abstraction of the methods and not actually nodejs general syntax for either the native node driver or the Mongoose syntax, but it does describe the basic premise.
So as long as you are using a "default" implementation method that returns the "original" state of the document before it was modified the you can play "spot the difference" as it were, and as is shown in the code example.
But doing this over general "update" operations is just not possible, as they are designed to possibly affect one or more objects and never return this detail.