Handling concurrent modification w/ mongoose - node.js

I have a couple places in my application that uses mongoose where I need to handle concurrent modification.
I know there is a version '__v' in all my documents. Most everything I look at points back to this blog:
http://aaronheckmann.tumblr.com/post/48943525537/mongoose-v3-part-1-versioning
Which outlines using the __v field to protect against concurrent array modification within the document. ie:
posts.update({ _id: postId, __v: versionNumber }
, { $set: { 'comments.3.body': updatedText }})
Do mongoose use the __v field automatically in any way to validate documents when they are saved? Or do you always have to add it to your query statement?

After reviewing the Mongoose source codes, the __v is really only used for arrays, the VERSION_WHERE is set when updating the array path.
if ('$push' == op || '$pushAll' == op || '$addToSet' == op) {
self.$__.version = VERSION_INC;
}
// now handling $set, $unset
else if (/\.\d+\.|\.\d+$/.test(data.path)) {
// subpath of array
self.$__.version = VERSION_WHERE;
}
And per this answer,
var t = Test();
t.name = 'hi'
t.arr = [1, 2, 3, 4, 5, 6];
t.save(function (err, result) {
console.log(result);
// use Mongoose pull method on the array
t.arr.pull(3);
t.save(function(err2, result2) {
console.log(result2)
});
});
Results:
{ __v: 0,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 3, 4, 5, 6 ] }
{ __v: 1,
name: 'hi',
_id: 53f59d2a6522edb12114b98c,
arr: [ 1, 2, 4, 5, 6 ] }

Related

Having problems adding a new data point inside and object in an array using for each

I'm having trouble trying to add a new entry into an existing array. I'm getting the data from mongoDB. If I change a value currently in the object/array with the same method is works fine. The documentation I've looked at seems to look the same as what I'm trying to do. Guess I'm missing something?
var managers = await User.find({
accessLevel: [1, 2]
});
managers.forEach((manager) => {
manager.newValue = 1;
console.log(manager.newValue)
console.log(manager)
})
Console output is:
1
{
_id: ObjectId("62dc2a79c71582db37858ad4"),
cashierId: 5,
password: '#',
firstName: '5',
lastName: '5',
accessLevel: 1,
lastLogin: 2022-08-29T15:00:28.074Z,
roles: null,
nextTest: 2022-07-23T17:06:01.823Z,
totalTest: 0,
totalScore: 0,
__v: 0
}
Expected output:
1
{
_id: ObjectId("62dc2a79c71582db37858ad4"),
cashierId: 5,
password: '#',
firstName: '5',
lastName: '5',
accessLevel: 1,
lastLogin: 2022-08-29T15:00:28.074Z,
roles: null,
nextTest: 2022-07-23T17:06:01.823Z,
totalTest: 0,
totalScore: 0,
newValue: 1,
__v: 0
}
This if the code snippet with for loop.
var managers = await User.find({
accessLevel: [1, 2] });
const dataMonth = await Check.find({})
const dataRequiredMonth = []
dataMonth.forEach((test) => {
if (test.dateConducted.getMonth() === dayjs().month())
dataRequiredMonth.push(test)
})
for (let i = 0; i < managers.length; i++) {
managers[i].newValue = 1;
console.log(managers[i].newValue)
console.log(managers[i])
}
forEach(...) basically execute the callback for each element of specified array with element passed as an argument to callback.
this argument are local to function and thus any change made on that argument will not reflect on original elements
Try using simple for(...) loop it will resolve your issue.
forEach() does not mutate the array on which it is called -Mozila
for more information about forEach method

Mongodb query params ( combine $gt with $in )

I'm using api query params library
rooms=2,4,5&rooms>=6
this gives
rooms: { '$gte': 6, '$in': [ 2, 4, 5 ] }
This won't work but if i search with only rooms>=6 it works or with rooms=2,4,5.
How i can combine $gte with $in ?
It sounds like you want to OR if the same key has multiple conditions. (I want all rooms, 2, 4, 5 OR greater than 6.)
To do that in mongo:
$or: [
{rooms: { $gte: 6}},
{rooms: { $in: [2, 4, 5]}}
]
You can also use not in for your query like
db.inventory.find( { rooms: { $nin: [ 1, 6] } } )
So guys i did this with $nin operator.
First i'm using api-query-params library.
let { filter, skip, limit, sort } = aqp(req.query)
It can parse query param --- rooms=1,2,3,4,5
into 'rooms': { '$in': [ 4, 3, 2, 1, 0, 5 ] }
But if we want when user adds 5 to be $gte 5
function addOrOperator(filter, field) {
//if selected value is undefined return
if(!filter[field]) return;
//values to match
let array = [0,1,2,3,4,5];
//if 5 is only selected returned value is 'rooms': 5
if(filter[field] === 5) {
filter[field] = {
$gte: 5
}
}
if(filter[field].$in) {
if(filter[field].$in.includes(5)) {
let difference = array.filter(x => !filter[field].$in.includes(x));
filter[field] = {
$nin: [...difference, null, ""]
}
}
}
return filter;
}
Then we are calling this function inside controller
addOrOperator(filter, 'rooms'); // all filters and key that we want to select

nodejs+mongodb access node.js object with document's _id field value in query

I am learning Mongodb to use with NodeJS. The below is my code:
let configMap = {
'1': 10,
'2': 12,
'3': 13
}
let myData = await mongo_groups_collection.find({
_id: {
$in: [1,2,3]
},
active: true
}).project({
'_id': 1,
'name': 1,
'members': 1,
'messages': {
$slice: [-(configMap[_id]), (DEFAULT_PAGE_SIZE_FILL * PAGE_SIZE) + (PAGE_SIZE - ((configMap[_id]) % PAGE_SIZE))] //Need to use _id to get configMap value
}
}).toArray();
I am using "configMap" nodeJS variable in Mongo slice function to retrieve certain number of elements from the array. To retrieve I need to lookup using "_id" field value which I am not getting and encounter _id "undefined" error.
That's because (configMap[_id]) gets compiled in node.js code but not on database as a query. So in code when this query gets compiled it's not able to find _id since you've not declared it as the way you did it for configMap variable.
You're expecting _id value to be actual _id from document. So, in case if you use (configMap[$_id]) it doesn't work that way cause you can't club a javascript object with documents field.
You can still do this using aggregate query like below - where we actually add needed v value as a field to document for further usage :
/** Make `configMap` an array of objects */
var configMap = [
{ k: 1, v: 10 },
{ k: 2, v: 12 },
{ k: 3, v: 13 },
];
let myData = await mongo_groups_collection.aggregate([
{
$match: { _id: { $in: [1, 2, 3] }, active: true }
},
/** For each matched doc we'll iterate on input `configMap` array &
* find respective `v` value from matched object where `k == _id` */
{
$addFields: {
valueToBeSliced: {
$let: {
vars: { matchedK: { $arrayElemAt: [ { $filter: { input: configMap, cond: { $eq: ["$$this.k", "$_id"] } } }, 0 ] } },
in: "$$matchedK.v",
}
}
}
},
{
$project: {
messages: { $slice: ["$messages", { $subtract: [0, "$valueToBeSliced"] }, someInputValue ] },
name: 1, members: 1
}
}
]).toArray();
Ref : $let
Note :
In projection you don't need to mention this '_id': 1 as it's included by default.
Since your query is using .find() you might be using $slice-projection-operator which can't be used in aggregation, instead we need to use $slice-aggregation-operator, both does same thing but have syntax variation, you need to refer to docs for that.
Also in someInputValue you need to pass in value of (DEFAULT_PAGE_SIZE_FILL * PAGE_SIZE) + (PAGE_SIZE - ((configMap[_id]) % PAGE_SIZE)) - So try to use aggregation operators $divide to get the value. We're doing { $subtract: [0, "$valueToBeSliced"] } to convert positive valueToBeSliced to negative as we can't just do like -valueToBeSliced which we usually do in javaScript.

Mongoose - remove multiple documents in one function call

In documentation there's deleteMany() method
Character.deleteMany({ name: /Stark/, age: { $gte: 18 } }, function (err) {});
I want to remove multiple documents that have one common property and the other property varies. Something like this:
Site.deleteMany({ userUID: uid, id: [10, 2, 3, 5]}, function(err) {}
What would be the correct syntax for this?
I believe what youre looking for is the $in operator:
Site.deleteMany({ userUID: uid, id: { $in: [10, 2, 3, 5]}}, function(err) {})
Documentation here: https://docs.mongodb.com/manual/reference/operator/query/in/
You can also use.
Site.remove({ userUID: uid, id: { $in: [10, 2, 3, 5]}}, function(err, response) {});
I had to change id to _id for it to work:
Site.deleteMany({ _id: [1, 2, 3] });
This happens if no id is defined and the default one is used instead:
"Mongoose assigns each of your schemas an _id field by default if one is not passed into the Schema constructor." mongoose docs
Yes, $in is a perfect solution :
Site.deleteMany({ userUID: uid, id: { $in: [10, 2, 3, 5] } }, function(err) {})
db.collectionName.deleteMany({key : value},function(err){
if(err){
console.log(err);
}
else{
console.log("All data deleted");
}
});

insert array using insert many function of mongoose

This is my Schema,
var trainListSchema = new mongoose.Schema({
trainNo: Number,
trainName:String,
fromStation : String,
toStation: String,
runningDays: [{type:Number}],
trainType: String,
createdTime: {type:Date , default:Date.now}})
and this is the function used to insert multiple documents.
createTrainList: function(data) {
trainList.insertMany(data, function(err, post) {
if (err) return next(err);
console.log(post);
});
}
Now as you can se runningDays is an array. While using the above method to insert data. runningDays gets inserted as 0 elements while rest of the field gets inserted successfully.
How should I go about inserting array within array using mongoose.
Help will be appreciated.
Here is the sample JSON.
[
{
"trainNo": "10104",
"trainName": "MANDOVI EXPRESS",
"fromStation": "MAO",
"toStation": "CSTM",
"days": [
0,
1,
2,
3,
4,
5,
6
],
"trainType": "MAIL EXP"
},
{
"trainNo": "10111",
"trainName": "KONKAN KANYA EX",
"fromStation": "CSTM",
"toStation": "MAO",
"days": [
0,
1,
2,
3,
4,
5,
6
],
"trainType": "MAIL EXP"
}]

Resources