Node js asynchronous mongodb find() query multiple calls - node.js

I fetched the records using
find().toArray()
query. In that records, there is relation id of other document(table). I want to get records of relation table of each record of above find query result.
Like:
db.collection('serviceBooking').find({'request_to_sp_user_id': docs._id.toString()}).toArray(function (err, serviceBookingDocs) {
if (serviceBookingDocs.length) {
var asyncCalls = [];
serviceBookingDocs.forEach(function (bookingRecord, key) {
var temp = {};
temp.userDetails = {};
//Async call for getting the user details for all users
asyncCalls.push(function (callback) {
db.collection('userDetails').findOne({'user_id': new mongo.ObjectID(bookingRecord.booked_by_user_id)}, function (err, userDetailsDocs) {
db.collection('serviceBookingDetails').find({'serviceBookingId': bookingRecord._id.toString()}).toArray(function (err, bookingDetailsDocs) {
if (userDetailsDocs) {
if (bookingDetailsDocs.length) {
temp.bookingDetails = bookingDetailsDocs;
bookingDetailsDocs.forEach(function (bookDetailItems, key) {
db.collection('serviceCatalog').findOne({'_id': new mongo.ObjectID(bookDetailItems.catalogId), isDeleted: 0}, function (err, spCatalogs) {
db.collection('spServiceCatalog').findOne({'_id': new mongo.ObjectID(spCatalogs.serviceCategory)}, function (err, spServiceCatalogDocs) {
if (spCatalogs) {
(spServiceCatalogDocs)
spCatalogs.catalogName = spServiceCatalogDocs.name;
temp.bookingDetails[key].serviceCatalgs = spCatalogs;
} else {
spCatalogs.catalogName = null;
temp.bookingDetails[key].serviceCatalgs = spCatalogs;
}
callback(null, temp);
})
})
})
}
} else {
callback(null, null);
}
})
})
})
})
}
})
I tried with callback function but it not get the values of category name from mainCategory document.
I also tried to get the internal fetched category name outside the forEach() but its not getting in result in temp array.

This may help you.
It says..
Functions are the only thing on javascript that "enclose" scope.
This means that the variable items in your inner callback function are not accessible on the outer scope.
You can define a variable in the outer scope so it will be visible to all the inner ones:

Related

Issue with asynchronous mongodb query

I am trying to loop through an array and find the amount of tickets assigned to each person.
Unfortunately, I noticed that my taskcount is getting the same values but in different order, because of its asynchronous nature.
Some queries might take long and so the ones that gets finished first gets inserted and hence my array has the same values but in different order. Now, I want to avoid that and make it so, that once a query gets completed, only then the next value from the array is being picked up and pushed to search from the db. How can i modify my existing code.
exports.find_task_count = function(callback) {
var names = ['Evan', 'Surajit', 'Isis', 'Millie', 'Sharon', 'Phoebe', 'Angel', 'Serah']
var taskcount = []
var resultsCount = 0;
for (var i = 0; i < names.length; i++) {
_tasks.find({'assignee': names[i]}, function (err, tickets) {
resultsCount++
if (err) {
console.log(err)
return callback(err)
} else {
taskcount.push(tickets.length)
if (resultsCount === names.length) {
return callback(taskcount);
taskcount=[]
}
}
})
}
}
You can use the async module designed to handle such scenarios.
I have updated the code as follows
var async = require('async');
exports.find_task_count = function (callback) {
var names = ['Evan', 'Surajit', 'Isis', 'Millie', 'Sharon', 'Phoebe', 'Angel', 'Serah'];
async.map(names, function (name, iterateeCallback) {
_tasks.find({ 'assignee': name }, function (err, tickets) {
if (err) {
return iterateeCallback(err);
}
return iterateeCallback(null, tickets.length);
});
}, function (error, results) {
if (error) {
return callback(error);
}
return callback(null, results);
});
}
As per the documentation of async
Note, that since this function applies the iteratee to each item in parallel, there is no guarantee that the iteratee functions will complete in order. However, the results array will be in the same order as the original coll.
if you still want to process the array in series use mapSeries instead of map in the above code

Assign keystonejs callback function data to array

I'm new to node.js and currently working on a project using keystonejs cms and MongoDB. Now I'm stuck in getting data related to multiple collections. Because of this callback functions, I couldn't return an array with relational data. My code something similar to this sample code.
var getAgenda = function(id, callback){
callback = callback || function(){};
if(id){
AgendaDay.model.find({summit:id}).exec(function (err, results3) {
var arr_agenda = [];
var arr_agenda_item = [];
for(var key3 in results3){
AgendaItem.model.find({agendaDay:results3[key3]._id}).exec(function (err, results2){
for(var key2 in results2){
arr_agenda_item.push(
{
item_id: results2[key2]._id,
item_name: results2[key2].name,
from_time: results2[key2].time_from,
to_time: results2[key2].time_to,
desc: results2[key2].description,
fatured: results2[key2].featured,
}
);
}
arr_agenda.push(
{
name: results3[key3].name,
date: results3[key3].date,
description: results3[key3].description,
item_list:arr_agenda_item
}
);
return callback(arr_agenda);
});
}
});
}
}
exports.list = function (req, res) {
var mainarray = [];
Summit.model.find().exec(function (err, resultssummit) {
if (err) return res.json({ err: err });
if (!resultssummit) return res.json('not found');
Guest.model.find().exec(function (err, resultsguset) {
for(var key in resultssummit){
var agen_arr = [];
for(var i=0; i<resultssummit[key].guests.length; i++){
var sumid = resultssummit[key]._id;
//this is the function im trying get data and assign to mainarray
getAgenda(sumid, function(arr_agenda){
agen_arr = arr_agenda;
});
mainarray.push(
{
id: resultssummit[key]._id,
name: resultssummit[key].name,
agenda_data: agen_arr,
}
);
}
res.json({
summit: mainarray,
});
}
});
}
}
If anyone can help me out, that would be really great :)
You need to restructure this whole thing. You should not be calling mongo queries in a for loop and expecting their output at the end of the loop. Also, your response is in a for loop. That won't work.
I'll tell you how to do it. I cannot refactor all of that code for you.
Instead of putting mongodb queries in a for loop, you need to convert it in a single query. Just put the _ids in a single array and fire a single query.
AgendaItem.model.find({agendaDay:{$in:ARRAY_OF_IDS}})
You need to do the same thing for AgendaDay.model.find({summit:id}) as well.

MongoDB multiple async queries return unordered result

I have an array of Courses [course1, course2, ..., courseN]. Each course has a Hero UUID, which matches the UUID of an object in another collection.
// This is actually another db query but imagine it's an array
var courses = [{
"courseName": "Sample course name 1",
"hero": "a3f6f088-7b04-45e8-8d3b-d50c2d5b3a2d"
}, {
"courseName": "Sample course name 2",
"hero": "1b46227a-c496-43d2-be8e-1b0fa07cc94e"
}, {
"courseName": "Sample course name 3",
"hero": "c3bae6bf-2553-473a-9f30-f5c58c4fd608"
}];
I need to iterate over all courses, get the hero uuid and do a query to the Heroes collection then when the query is complete add the hero information to the course object.
The problem is that all queries are fired so rapidly that MongoDB returns them in arbitrary order. It receives all 3 hero uuids in order but it will sometimes return the third one before the first one, etc. Is there a way for one query to complete then do the other one, etc.?
What I am doing right now is:
var newCourses = courses;
var counter = 0;
courses.forEach(function (course) {
var courseHeroUuid = course.hero;
// This function does the query by uuid and returns the doc
getHeroByUuid(courseHeroUuid, function (err, result) {
if (err) {
next(err);
}
// Replace the hero UUID with the hero document itself
newCourses[counter].hero = result[0];
if (++counter == courses.length) {
next(null, newCourses);
}
}
});
This is a function inside an async.waterfall array, this is why I track the counter and call next() to go on. I know I can use async.each for the iteration, I tried it didn't help out.
This is the query I am doing.
function getHeroByUuid(heroUuid, callback) {
Hero.find({uuid: heroUuid}, function (err, result) {
if (err) {
callback(err);
}
callback(null, result);
})
}
This happens:
http://i.imgur.com/mEoQfgH.png
Sorry to answer my own question but I figured it out. What I needed was right under my nose.
I ended up using the async.whilst() function, documentation is right here and does exactly what I need - execute the next iteration of the loop after the result from the previous one is returned.
My code now looks like this:
var newCourses = courses;
var courseItemsLength = courses.length;
var counter = 0;
async.whilst(
function () {
return counter < courseItemsLength;
}, function (callback) {
var heroUuid = allCourses[counter].hero;
getHeroByUuid(heroUuid, function (err, result) {
if (err) {
next(err);
}
newCourses[counter].hero = result.name;
counter++;
if (err) {
callback(err);
}
callback();
});
}, function (err) {
if (err) {
next(err);
}
next(null, newCourses);
});

transactions in Arangodb

I have some problem with transactions in ArangoDB+nodejs. I need to do something like this:
transaction
{
insertedId=insertItemOneInDB();
insertItemTwoInDB();
}
but when the second insert failed, the first one didn't rollback!
please help me with an example!
here is my code:
var transaction = function (collections,params,callback)
{
try
{
db.transaction.submit("user_merchant_tbl",params,
function ()
{
console.log("_collections:",collections);
console.log("params:");
console.log(params);
console.log("---");
console.log("+==========+");
//
var insertedDataId;
var relationsArrayIds=[];
db.document.create(collections,params.data).then(function(_insertedId)
{
insertedDataId=_insertedId;
}
,function(err)
{
console.log("ERROR: Arango--insert-->err: %j", err);
//throw "Error: "+err;
return false;
});
/////
var relations=params.relations;
for(var i=0;i<relations.length;i++)
{
db.document.create(relations[i].edge,relations[i].data).then(
function(_id)
{
relationsArrayIds.push(_id);
next(true);
}
,function(err)
{
console.log("ERROR: Arango--insert.edge-->err:23232 %j", err);
console.log("after return");
next(false);
return false
});
}
console.log("transaction before true");
function next(result)
{
if(result==true)
{
console.log("transaction is ok:",result);
callback(insertedDataId,result);
}
else
{
console.log("transaction is not OK:",result);
callback(insertedDataId,false);
}
}
}
);
}
catch(e)
{
console.log("catch->error in -->Arango.transaction: ",e);
}
}
first of all there seems to be a misunderstanding in how to write the action that is supposed to be executed. This action is executed directly on the Database Server , hence you cant use any functionality provided by the Arango Javascript api.
If you want to design your action it has to run in the arango shell or on the server console (bin/arangod data --console)
I took a look into your code and assume you want to store relations between users and merchants. As Arango comes with a nice graph module you could follow the following approach :
// First we define a graph, containing of 2 document collections ("users" and "merchants") and 2 edge collections (one per relation type, in this example "contactRequested" and "boughtSomethingFrom".
// Note that in this definition the relation "boughtSomethingFrom" is only allowed from a user to a merchant. Of course this is just one way to design it, you have to do it the way it suits you the best.
var edgeDefinitions = [{
collection: "contactRequested",
from: ["users", "merchants"],
to: ["users", "merchants"]
}, {
collection: "boughtSomethingFrom",
from: ["users"],
to: ["merchants"]
}];
// Now we create a graph called "user_merchant_graph" and in the callback function execute a transaction
db.graph.create("user_merchant_graph", edgeDefinitions, function(err, ret, message) {
// Lets define the action for the transaction, again this will be executed directly on the server ......
var action = function (params) {
// We have to require the database module ....
var db = require("internal").db;
var relationsArrayIds = [];
// now we store the user provided to the function
var insertedUserId = db["users"].insert(params.data)._id;
var relations = params.relations;
// Now we loop over through the relations object, store each merchant and it's relations to the user
Object.keys(relations).forEach(function (relation) {
// store merchant
var insertedMerchantId = db["merchants"].insert({merchantName : relation})._id;
// store relation as edge from "insertedUserId" to "insertedMerchantId".
var edgeId = db[relations[relation].relation].insert(insertedUserId, insertedMerchantId, relations[relation].additionalData)._id;
relationsArrayIds.push(edgeId);
});
};
// End of action
var options = {};
options.params = {
data: {
userName : "someUserName",
userSurname : "someUserSurname"
},
relations : {
merchantA : {relation : "contactRequested", additionalData : {data :"someData"}},
merchantB : {relation : "boughtSomethingFrom", additionalData : {data :"someData"}},
merchantC : {relation : "contactRequested", additionalData : {data :"someData"}}
}
};
// Now we call the transaction module ... a note to the collections parameter, it has to be an object containing the keys "write" and "read" which have a list of all collections as value into which the action is writing /reading from
// This collections object is NOT available within your action, the only thing passed as argument to your action is "options.params" !!
db.transaction.submit({write : ["users", "merchants", "contactRequested", "boughtSomethingFrom"]}, action, options, function(err, ret, message) {
//some callback
});
});
With regards to transactions they are working, you can give this code a shot and if you f.e. mess up the storing of the edges (change it to "var edgeId = db[relations[relation].relation].insert(relations[relation].additionalData)._id;")
you will see that your user and merchant have not been stored
I hope this helps

NodeJS + Mongoose: Updating all fields on a Mongoose model

I'm building out an api using Node, MongoDB and Mongoose. One thing that is bugging me is that you can't seem to set multiple fields at once:
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
doc.update(req.params);
doc.save();
...
However, it seems that you have to work out the update query and run it on the Model object rather than on the document object. Unless you want to assign individual properties and run save() at the end.
Is there any way of accomplishing this without having to write a Mongo query?
jsaak's answer is good but doesn't work for nested objects. I elaborated on his answer by searching and setting nested objects.
I added these functions to a utility.js file
var _ = require('underscore');
exports.updateDocument = function(doc, SchemaTarget, data) {
for (var field in SchemaTarget.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
var newValue = getObjValue(field, data);
console.log('data[' + field + '] = ' + newValue);
if (newValue !== undefined) {
setObjValue(field, doc, newValue);
}
}
}
return doc;
};
function getObjValue(field, data) {
return _.reduce(field.split("."), function(obj, f) {
if(obj) return obj[f];
}, data);
}
function setObjValue(field, data, value) {
var fieldArr = field.split('.');
return _.reduce(fieldArr, function(o, f, i) {
if(i == fieldArr.length-1) {
o[f] = value;
} else {
if(!o[f]) o[f] = {};
}
return o[f];
}, data);
}
implement as:
var util = require('./utility');
app.put('/record/:id', function(req, res) {
Record.findById(req.params.id, function(err, doc) {
if (!err) {
utils.updateDocument(doc, Record, req.params);
doc.save();
...
Maybe this has changed since this question was first asked, but you can update multiple paths in Mongoose with the set method ike:
// object
doc.set({
path : value,
path2 : {
path : value
}
});
doc.save();
References
http://mongoosejs.com/docs/api.html#document_Document-set
direct updating is not recommended according to this document:
http://mongoosejs.com/docs/2.7.x/docs/updating-documents.html
i solved it like this:
Book.findOne({isbn: req.params.isbn}, function (err, book){
if (err) {
res.send(422,'update failed');
} else {
//update fields
for (var field in Book.schema.paths) {
if ((field !== '_id') && (field !== '__v')) {
if (req.body[field] !== undefined) {
book[field] = req.body[field];
}
}
}
book.save();
}
});
If you want to update the entire document , you can delete the document based on its id and store the entire object again.
That object must contain data for each and every fields of the mongo document.
Here is an example.
mongoDBCollectionObject.findOneAndRemove({ // -- it will delete the entire document
_id: req.body.fieldsdata._id // here fiedsdata is exact copy with modification of previous data
}, function(err, data) {
var newFieldsData = new mongoDBCollectionObject(fieldsdata); //-- fieldsdata updated data
newFieldsData.save(function(err, data) { // save document to that collection with updated data
if (err) {
console.log(err);
} else
res.json({
success: true
});
});
})
To clarify the question, it looks like you are taking the Request parameters and using those to find and update the given document.
Is there any way of accomplishing this without having to write a Mongo query?
The obvious answer is to update the Model object with the value from the Request. Which is what you suggest...
Unless you want to assign individual properties and run save() at the end.
But it seems like you don't want to do this? It sounds like you want to update the Model object directly from the Request object?
You can do this if you really want. You just loop through req.params and set the doc values where appropriate.
for(var i in req.params) {
if(req.params[i] != doc[i]){
doc[i] = req.params[i];
}
}
It should be as simple as this. However, you only want to do this if you have a whole bunch of validation code on the Model objects. The whole point to the Model is that you don't want to get random data in the DB. The line above will generically "set" the correct values, but you'll definitely need to include code for authentication, authorization and validation around that simple for loop.
try to updating the collection without the find, like this
Record.update({_id:req.params.id}, {$set: { field: request.field }}, {upsert: true}, function(err{...})
The option upsert create the document if not exist.
In case you have a new object and want to update whole object in the database, you can update multiple fields at once like this:
find the object
get all schema paths (fields)
save the new object.
SomeModel.findOne({ 'id': 'yourid' },function (err, oldObject) {
if (err) return handleError(err);
// get all schema paths (fields)
SomeModel.schema.eachPath(function(path) {
// leave __id and __v alone
if (path != '_id' && path != '__v') {
// update the data from new object
oldObject[path] = newObject[path];
}
})
oldObject.save(function(err) {
if (err)
console.log(err)
});
})
A neat and clean approach would be using async await and findOneAndRemove along with create Here is the sample code
try {
let resp = await this.findOneAndRemove({ _id: req.body._id });
let entry = await this.create(req.body);
} catch (err) {
}
Don't Forget to mark this whole function as async

Resources