UPDATED WITH FIX: Is there a way to loop an object and save/update a mongodb document on each iteration without getting ParallelSaveError? - mongodb

I want to update the mongodb document on every loop but seems not to work. I used map to loop through the object and was getting a
parallelSaveError: Can't save() the same doc multiple times in parallel
So now using async.each to loop through the object but seems mongoose only save once. Code below
const openTrade = await TradeModel.find({
$and: [{ isOpen: true }, { price: { $lte: price } }]
})
.sort("volume")
.where({ userId: { $ne: req.authUser._id } });
trade = async openTrade => {
const buyerInfo = await UserModel.findById(req.authUser._id);
const buyerWallet = await WalletModel.findById(buyerInfo.walletId);
const sellerInfo = await UserModel.findById(openTrade.userId);
const sellerWallet = await WalletModel.findById(sellerInfo.walletId);
buyerWallet.balance = parseInt(buyerWallet.balance) - amountToPay;
sellerWallet.balance = parseInt(sellerWallet.balance) + amountToPay;
await Promise.all([sellerWallet.save(), buyerWallet.save()]);
};
async.forEachOf(openTrade, trade);
So I finally found the issue. I did not make the buyerInfo, sellerInfo and Wallets variable constant. So now it works. Thanks

You can try as below: As according to MongoDB update If the document contains an _id field, then the save() method is equivalent to an update with the upsert option set to true and the query predicate on the _id field.
const openTrade = await TradeModel.find({$and:[{isOpen : true}, {price : {$lte : price}}]}).sort('volume').where({userId : {$ne : req.authUser._id } })
trade = async (openTrade)=>{
buyerInfo = await UserModel.findById(req.authUser._id)
buyerWallet = await WalletModel.findById(buyerInfo.walletId)
sellerInfo = await UserModel.findById(openTrade.userId)
sellerWallet = await WalletModel.findById(sellerInfo.walletId)
delete buyerWallet._id;
delete sellerWallet._id;
buyerWallet.balance = parseInt(buyerWallet.balance) - amountToPay
sellerWallet.balance = parseInt(sellerWallet.balance) + amountToPay
await Promise.all([sellerWallet.save(), buyerWallet.save()])
}
async.forEachOf(openTrade, trade)

Related

Query to mongo in presave with NestJS

I´m trying to generate an autoincremental value in an Schema using Mongoose with MongoDB in a NestJS project.
The idea is the to replicate this (from here Mongoose auto increment) but with Nest.
var entitySchema = mongoose.Schema({
testvalue:{type:String,default:function getNextSequence() {
console.log('what is this:',mongoose);//this is mongoose
var ret = db.counters.findAndModify({
query: { _id:'entityId' },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
}
});
I was rewriting this in the following wayÑ
UserSchema.pre<User>('save', async function (next: any) {
const userModel = new Model<UserDocument>();
const last = await userModel.findOne({}).sort({ _id: -1 }).exec();
this.number = last.number ? last.number + 1 : 1;
next();
});
The issue that I found is that the presave code is not being executed, and also I have some doubts about the instance of Model that I´m not sure it will work in that way (or if it is the right way to do it).

Updating sub document using save() method in mongoose does not get saved in database and shows no error

I have a Mongoose model like this:
const centerSchema = mongoose.Schema({
centerName: {
type: String,
required: true,
},
candidates: [
{
candidateName: String,
voteReceived: {
type: Number,
default: 0,
},
candidateQR: {
type: String,
default: null,
},
},
],
totalVote: {
type: Number,
default: 0,
},
centerQR: String,
});
I have a Node.JS controller function like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
newCenter.candidates.forEach(async (candidate, i) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
// ** Tried these: **
// newCenter.markModified("candidates." + i);
// candidate.markModified("candidateQR");
});
// * Also tried this *
// newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Simply, I want to modify the candidateQR field on the subdocument. The result should be like this:
{
"centerName": "Omuk Center",
"candidates": [
{
"candidateName": "A",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda276"
},
{
"candidateName": "B",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda277"
},
{
"candidateName": "C",
"voteReceived": 0,
"candidateQR": "some random qr code text",
"_id": "624433fc5bd40f70a4fda278"
}
],
"totalVote": 0,
"_id": "624433fc5bd40f70a4fda275",
"__v": 1,
}
But I am getting the candidateQR still as null in the Database. I tried markModified() method. But that didn't help (showed in the comment section in the code above). I didn't get any error message. In response I get the expected result. But that result is not being saved on the database. I just want candidateQR field to be changed. But couldn't figure out how.
forEach loop was the culprit here. After replacing the forEach with for...of it solved the issue. Basically, forEach takes a callback function which is marked as async in the codebase which returns a Promise initially and gets executed later.
As for...of doesn't take any callback function so the await inside of it falls under the controller function's scope and gets executed immediately. Thanks to Indraraj26 for pointing this out. So, the final working version of the controller would be like this:
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
for(const candidate of newCenter.candidates) {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
candidate.candidateQR = candidateQRGen;
};
newCenter.markModified("candidates");
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
Also, shoutout to Moniruzzaman Dipto for showing a different approach to solve the issue using async.eachSeries() method.
You can use eachSeries instead of the forEach loop.
const async = require("async");
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
async.eachSeries(newCenter.candidates, async (candidate, done) => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString(),
);
candidate.candidateQR = candidateQRGen;
newCenter.markModified("candidates");
await newCenter.save(done);
});
res.status(201).json(newCenter);
};
As far as I understand, you are just looping through the candidates array but you
are not storing the updated array. You need to store the updated data in a variable as well. Please give it a try with the solution below using map.
exports.createCenter = async (req, res, next) => {
const newCenter = await Center.create(req.body);
let candidates = newCenter.candidates;
candidates = candidates.map(candidate => {
const candidateQRGen = await promisify(qrCode.toDataURL)(
candidate._id.toString()
);
return {
...candidate,
candidateQR: candidateQRGen
}
});
newCenter.candidates = candidates;
const upDatedCenter = await newCenter.save();
res.status(201).json(upDatedCenter);
};
You can use this before save()
newCenter.markModified('candidates');

Delete Duplicates MongoDB

When web-scraping my code is sometimes fetching duplicate entries to MongoDB Realm. How can I make my MongoDB database contain only unique entries and remove duplicates?
Below is the code from MongoDB Realm:
exports = async function(payload, response) {
const {jobsPerPage = 100, page = 0} = payload.query;
let query = {};
if (payload.query.jobPosition) {
query = { $text: { $search: payload.query.jobPosition } }
}
const collection = context.services.get("mongodb-atlas").db("sample_jobs").collection("glassdoordbs");
let jobsList = await collection.find(query).sort({featured: -1}).skip(page*jobsPerPage).limit(jobsPerPage).toArray()
let allResults = await collection.count(query).then(num => num.toString())
jobsList.forEach(job => {
job._id = job._id.toString();
});
const responseData = {
jobs: jobsList,
page: page.toString(),
filters: {},
entries_per_page: jobsPerPage.toString(),
total_results: await collection.count(query).then(num => num.toString()),
totalPages: await Math.ceil(allResults / jobsPerPage).toString()
};
return responseData;
};
An example of duplicate entries I get:

MongoDB mongoose findOneAndUpdate() Await does not wait. A3 runs before A2

A3 log runs before A2. How do I fix this? I want to wait until the update returns and use the result for the next logic. Thanks in Advance.
const MongoClient = require("mongodb").MongoClient;
console.log('A1');
// Inserting into DB
await db.collection(companyCollection).findOneAndUpdate({"_id": new mongo.ObjectID(companyId)}, {$set: company}, {upsert: false}, await async function (err, result) {
console.log('A2');
let resultRes = null;
if (err) {
resultRes = { success: false };
} else {
resultRes = { success: true };
}
return callback(err ? true : false, resultRes);
});
console.log('A3');
findOneAndUpdate has two signatures, either return a promise, or call a callback. Your version utilizes the callback version, hence adding the await is pointless because no promise is being returned.
Here's a simple re-write that utilizes the promise syntax:
const MongoClient = require("mongodb").MongoClient;
async function doSomething() {
console.log('A1');
// Inserting into DB
let result = await db.collection(companyCollection).findOneAndUpdate({"_id": new mongo.ObjectID(companyId)}, {$set: company}, {upsert: false});
console.log('A2');
... do whatever you want ...
console.log('A3');
}
return doSomething();

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});