Mongoose $inc updateMany cannot increment with non-numeric argument - node.js

Node API, where we have Mongo collection of profiles, and every profile have subscription_plan which represent rest of the days they have paid for using app.
Now, i works on part of the backend which should decrease subscription_plan of all profiles by 1.
The problem is, subscription_plan is declared as String, so I can't just decrease it by 1
I tried with this after getting some tips here:
router.put('/reduceSubscription', async (req, res) => {
try {
const updatedProfiles = await Profile.updateMany({ is_active: true }, [
{
$set: {
subscription_plan: {
$toString: {
$subtract: [{ $toInt: '$subscription_plan' }, 1]
}
}
}
}
]).exec();
console.log(updatedProfiles);
} catch (err) {
res.status(500).send({ msg: err.message });
}
});
After testing in postman, i got message:
"msg": "Failed to parse number 'NaN' in $convert with no onError value: Bad digit \"N\" while parsing NaN"
I would appreciate any help or code snippet that will help me solve this problem.
Thanks :)

What you are trying to do (as the error states) is not possible with the current schema simply because your Profile model schema defines subscription_plan as a String and $inc only works on numeric fields.
Either change the schema type for that field or use the aggregate pipeline to update the value by creating a pipeline that has a set of aggregation pipeline operations i.e. 1) get the current value, 2) convert it to a numeric value using $toInt, 3) decrement the value with $subtract and 4) set the new value back to string using $toString:
router.put('/reduceSubscription', async (req, res) => {
try {
await Profile.updateMany(
{ is_active: true },
[
{ '$set': {
'subscription_plan': {
'$toString': {
'$subtract': [
{ '$toInt': '$subscription_plan' },
1
]
}
}
} }
]
).exec();
res.json('Profiles updated');
} catch (err) {
res.status(500).send({ msg: err.message });
}
});

Related

I have 2 issues with mongoose aggregation and index method

I have 2 issues
FIRST ONE:
I am trying to make review schema that a user should add 1 review per bootcamp
Code:
ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true });
It doesnt work .. and the user still can add more than one review
SECOND ISSUE:
I am trying to calculate the averagerating of reviews but it doesn`t get added to the db when am fetching the bootcamps
Code:
// Static Method to get the avg rating of reviews and save
ReviewSchema.statics.getAverageRating = async function (bootcampId) {
const obj = await this.aggregate([
{
$match: { bootcamp: bootcampId },
},
{
$group: {
_id: '$bootcamp',
averageRating: { $avg: '$rating' },
},
},
]);
try {
await this.model('Bootcamp').findByIdAndUpdate(bootcampId, {
averageRating: obj[0].averageRating,
});
} catch (err) {
console.log(err);
}
//Call averageRating after save
ReviewSchema.post('save', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
//Call averageRating before remove
ReviewSchema.pre('remove', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
** It doesnt work and the averagerating never gets added to the database (as a bootcamp`s field)**
I Did the same as the tutorial and it didn`t work at the first but then i figured out that missing a semi-colon.

Mongodb update object in multi nested array

I'm trying to update an object that is inside a multi nested array in Mongodb. I know that I cannot use the positional $ operator for multi nested arrays. So I'm trying to use arrayFilters instead.
Here is my query structure:
var objTest = {
name: 'blah',
color: 'blablah'
}
SomeModel.updateOne(
{
an_id: anId,
"an_array.another_array.and_another_array.some_id": id,
},
{
$set: {
"an_array.$[element].another_array.$[another_element].and_another_array": objTest,
},
},
{
arrayFilters: [
{ "element.name": 'test' },
{ "another_element.id": 'test2' },
],
}
)
.then((result) => {
console.log("result ", result);
resolve(result);
})
.catch((err) => {
console.log("err is ", err);
reject(err);
});
So as per the example, I'm trying to update the object that matches some_id in the and_another_array array. When I try this, I'm receiving the error The top-level field name must be an alphanumeric string beginning with a lowercase letter, found 'another_element'
I don't understand what I'm doing wrong here.
I was able to figure out what the issue was. For some reason, mongodb didn't like the in another_element. renaming it to anotherelement worked.

Mongodb/mongoose omit a field in response [duplicate]

I have a NodeJS application with Mongoose ODM(Mongoose 3.3.1). I want to retrieve all fields except 1 from my collection.For Example: I have a collection Product Which have 6 fields,I want to select all except a field "Image" . I used "exclude" method, but got error..
This was my code.
var Query = models.Product.find();
Query.exclude('title Image');
if (req.params.id) {
Query.where('_id', req.params.id);
}
Query.exec(function (err, product) {
if (!err) {
return res.send({ 'statusCode': 200, 'statusText': 'OK', 'data': product });
} else {
return res.send(500);
}
});
But this returns error
Express
500 TypeError: Object #<Query> has no method 'exclude'.........
Also I tried, var Query = models.Product.find().exclude('title','Image'); and var Query = models.Product.find({}).exclude('title','Image'); But getting the same error. How to exclude one/(two) particular fields from a collection in Mongoose.
Use query.select for field selection in the current (3.x) Mongoose builds.
Prefix a field name you want to exclude with a -; so in your case:
Query.select('-Image');
Quick aside: in JavaScript, variables starting with a capital letter should be reserved for constructor functions. So consider renaming Query as query in your code.
I don't know where you read about that .exclude function, because I can't find it in any documentation.
But you can exclude fields by using the second parameter of the find method.
Here is an example from the official documentation:
db.inventory.find( { type: 'food' }, { type:0 } )
This operation returns all documents where the value of the type field is food, but does not include the type field in the output.
Model.findOne({ _id: Your Id}, { password: 0, name: 0 }, function(err, user){
// put your code
});
this code worked in my project. Thanks!! have a nice day.
You could do this
const products = await Product.find().select(['-image'])
I am use this with async await
async (req, res) => {
try {
await User.findById(req.user,'name email',(err, user) => {
if(err || !user){
return res.status(404)
} else {
return res.status(200).json({
user,
});
}
});
} catch (error) {
console.log(error);
}
In the updated version of Mongoose you can use it in this way as below to get selected fields.
user.findById({_id: req.body.id}, 'username phno address').then(response => {
res.status(200).json({
result: true,
details: response
});
}).catch(err => {
res.status(500).json({ result: false });
});
I'm working on a feature. I store a userId array name "collectedUser" than who is collected the project. And I just want to return a field "isCollected" instead of "collectedUsers". So select is not what I want. But I got this solution.
This is after I get projects from database, I add "isCollected".
for (const item of projects) {
item.set("isCollected", item.collectedUsers.includes(userId), {
strict: false,
})
}
And this is in Decorator #Schema
#Schema({
timestamps: true,
toObject: {
virtuals: true,
versionKey: false,
transform: (doc, ret, options): Partial<Project> => {
return {
...ret,
projectManagers: undefined,
projectMembers: undefined,
collectedUsers: undefined
}
}
}
})
Finally in my controller
projects = projects.map(i => i.toObject())
It's a strange tricks that set undefined, but it really work.
Btw I'm using nestjs.
You can do it like this
const products = await Product.find().select({
"image": 0
});
For anyone looking for a way to always omit a field - more like a global option rather than doing so in the query e.g. a password field, using a getter that returns undefined also works
{
password: {
type: String,
required: true,
get: () => undefined,
},
}
NB: Getters must be enabled with option { toObject: { getters:true } }
you can exclude the field from the schema definition
by adding the attribute
excludedField : {
...
select: false,
...
}
whenever you want to add it to your result,
add this to your find()
find().select('+excludedFiled')

how does one update numbers inside mongoose model nested array

can someone please explain what I am doing wrong. I am attempting to update a number value inside a nested array on my mongoose schema by adding two numbers
here is the section in question
$set: {
"shareHolders.$.shares": Number(req.existingStock) + Number(req.stock)
}
req.existing shares is say 100 and req.stock is a formatted as a string but equals say 100 so, in short, the new value for the shares should be 200
BUT when i run the code the shares of the said shareholder does not change it remains the original value.
here is the full snippet
module.exports.updateShareHolder = function(req, callback) {
console.log('updateShareHolder');
console.log(req);
console.log(req.existingStock + Number(req.stock));
Company.update({
"_id": req.companyID,
"shareHolders.userId": req.userID
}, {
$push: {
"shareHolders.$.agreements": {
agreementID: req.agreementID
}
}
}, {
$set: {
"shareHolders.$.shares": Number(req.existingStock) + Number(req.stock)
}
}, function(err) {
if (err) {
console.log(err);
callback(err, err);
} else {
console.log('updateShareHolder');
callback(null, 'success');
}
})
};
Convert to a number before doing your update.
const updatedStock = Number(req.existingStock) + Number(req.stock)
then
$set: {
"shareHolders.$.shares": updatedStock
}

Is it possible to get count of number of docs returned from find() query in mongoose

I am trying to get count of data fetched from the database using find() query in mongoose. Now can anyone tell me can i do something like below or do i have to write other function to do that
merchantmodel.find({merchant_id: merchant_id, rating: {'$ne': -1 }, review: {'$ne': "" }}, {'review':1, '_id':0}, {sort: {time_at: -1}}, function(err, docs) {
if (err) {
} else {
if (docs) {
console.log(docs[1].review);
console.log(docs.size()); // Here by writing something is it possible to get count or not
res.json({success: 1, message : "Successfully Fetched the Reviews"});
}
}
});
Convert returned value to array and then use length property
var query = { merchant_id : merchant_id, rating : { '$ne': -1 }, review: { '$ne': "" }};
var projection = { 'review':1, '_id':0 };
var options = { sort: { time_at: -1 } };
merchantmodel.find(query, projection, options).toArray(function(err, docs) {
if (err) {
throw(err);
}
console.log(docs[1].review);
console.log(docs.length);
res.json({success: 1, message : "Successfully Fetched the Reviews"});
});
You can simply do this:
console.log(docs.length);
The docs variable returned by the find() method is an array so docs.length would do the job.
The mongodb native way to do this would be:
db.collection.find( { a: 5, b: 5 } ).count()

Resources