Mongodb nested query with node js - node.js

I don't get results from nested query, loc is always null. The query parameter has proper value when I print it, and the database collection 'users' has documents with ids from the array friendsP.
var acquireFriendsPositions = function(db, id, res, callback) {
var cursor = db.collection('users').find({"_id" : new ObjectId(id)}, {_id:0, friends:1});
cursor.each(function(err, doc) {
assert.equal(err, null);
if (doc != null) {
friendsP = doc.friends;
console.log(friendsP); //I get the array friendsP
for(var i =0; i<friendsP.length; i++)
{
console.log(friendsP[i]); //friendsP[i] has proper value
var curs = db.collection('users').find({"_id" : new ObjectId(friendsP[i])}); //but query returns null
curs.each(function(err, loc) {
//assert.equal(err, null);
if(loc!= null) {
console.log(loc);
friendsPos.push(loc);
}
else {
console.log("else");
}
});
}
promise(friendsPos, res); //here i wait for friendsPos and use it in res.send(), but friendsPos is empty because loc is always null
} else {
callback(); //callback does db.close();
}
});
};

If this is the exact code that you are using I suspect that the friendsP value gets hoisted and overwritten in the next each cycle. Meaning that you should be able to fix this by simply changing the code to var friendsP = doc.friends so the friendsP variable is in the function scope. If this is what is happening this is a nasty bug and you should always the declare the variables with a local scope to prevent this from happening.

Try using this for casting Object Id:
var mongodb = require('mongodb');
mongodb.ObjectID.createFromHexString(friendsP[i]);

Thank you, guys. Actually, the problem was callback() which was closing the connection before queries were executed. Here is my new code:
var acquireFriendsPositions = function(db, id, res, callback) {
db.collection('users').findOne({"_id" : new ObjectId(id)},
function(err, item) {
var friendsP = item.friends;
var locFriends = [];
promise(locFriends, res);
var x = 0;
for(i =0; i<friendsP.length; i++)
{
db.collection('users').findOne({"_id" : friendsP[i]}, function(err,subItem){
x=x+1;
//console.log(subItem);
locFriends.push(subItem);
if(x==friendsP.length)
callback();
});
}
});
};

Related

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.

Nested query Synchronization in mongodb

I have following two collection:
1) Users: {name: xyz,
email:abc#xuz.com}
2) Posts: {_id: 12345678,
time:asdfg,
authEmail:abc#xyz.com,
description: asdigligvilud}
Here i want to get the details of each post along with the corresponding name of author id.
So i wrote query like this:
// get all the posts
Posts.find((err, posts)=> {
if (err) { next(err) };
var data = Array();
var count = 0;
var len = posts.length;
// function to check the end of inner queries
var checkloop = function(){
count++;
if(count===len)
return res.json({result:data,
msg:'success'});
}
for(var i=0;i<len;i++){
Users.findOne({email:posts[count].authEmail},(err,usr)=>{
if(usr){
var item = {
'authorName':usr.name?
'email':usr.email,
'postDesc':posts[count].desc,
'creationTime':posts[count].time
}
data.push(item);
}
checkloop();
});
}
});
But i am getting same result for each iterations that is the post deatils and author id corresponding to the very first loop that is posts[0] only.
So i think its because of asynchronous nature of queries. Is there any standard way to make such query in mongodb or do i need to change my callback method?
Thanks in advance.
I believe the issue you had was because count inside the for loop probably was meant to be i. In addition, each loop iteration captures the same variable i in the Users.findOne callback, but the callback should be able to refer to the post that was queried. Without making much changes, I think this can work with using forEach over the returned posts like below:
// get all the posts
Posts.find((err, posts)=> {
if (err) { next(err) };
var data = Array();
var count = 0;
var len = posts.length;
// function to check the end of inner queries
var checkloop = function(){
count++;
if(count===len)
return res.json({result:data,
msg:'success'});
}
posts.forEach((post) => {
Users.findOne({email:post.authEmail},(err,usr)=>{
if(usr){
var item = {
'authorName':usr.name?
'email':usr.email,
'postDesc':post.desc,
'creationTime':post.time
}
data.push(item);
}
checkloop();
}
});
});

Synchronous for loop in node js

So let's say I have the following for loop
for(var i = 0; i < array.length; i++){
Model.findOne({ _id = array[i].id}, function(err, found){
//Some stuff
});
}
How do I make this code work? Every time I run it I get array[i] = undefinedbecause the mongo-db query is asynchronous and the loop has already iterated 5 times by the time the first query is even completed. How do I go about tackling this issue and waiting for the query to complete before going on to the next iteration?
This doesn't specifically answer your question, but addresses your problem.
I'd use an $in query and do the filtering all at once. 20 calls to the db is pretty slow compared to 1:
// grab your ids
var arrayIds = myArray.map(function(item) {
return item._id;
});
// find all of them
Model.find({_id: {$in: arrayIds}}, function(error, foundItems) {
if (error) {
// error handle
}
// set up a map of the found ids
var foundItemsMap = {};
foundItems.forEach(function(item) {
foundItemsMap[item._id] = true;
});
// pull out your items that haven't been created yet
var newItems = [];
for (var i = 0; i < myArray.length; i++) {
var arrayItem = myArray[i];
if ( foundItemsMap[arrayItem._id] ) {
// this array item exists in the map of foundIds
// so the item already exists in the database
}
else {
// it doesn't exist, push it into the new array
newItems.push(arrayItem);
}
}
// now you have `newItems`, an array of objects that aren't in the database
});
One of the easiest ways to accomplish something like you want is using promises. You could use the library q to do this:
var Q = require('q');
function fetchOne(id) {
var deferred = Q.defer();
Model.findOne({ _id = id}, function(err, found){
if(err) deferred.reject(err);
else deferred.resolve(found);
});
return deferred.promise;
}
function fetch(ids, action) {
if(ids.length === 0) return;
var id = ids.pop();
fetchOne(id).then(function(model) {
action(model);
fetch(ids, action);
});
}
fetch([1,2,3,4,5], function(model) { /* do something */ });
It is not the most beautiful implementation, but I'm sure you get the picture :)
Not sure if this is the right way, it could be a bit expensive but this how i did it.
I think the trick is to pull all your data and then looking for an id match.
Model.find(function(err, data) {
if (err) //handle it
for (var i=0; i<array.length; i++) {
for (var j=0; ij<data.length; j++) {
if(data[j].id == array[i].id) {
// do something
}
}
}
}

MongoDB findOne error in for loop

function ParseOrderSchema(CartItems , callback)
{
var lookup = 0;
var subOrderList = new Array();
for(var i=0;i<CartItems.length;i++)
{
Meal.findOne({ _id: CartItems[i].id }).lean().exec(function (err, meal) {
console.log(CartItems[i]);
//meal.mealQTY = CartItems[i].qty;
var s = new subOrder({ meals: meal, deliveryDate: getMomentDate(0) });
subOrderList.push(s);
if (++lookup == CartItems.length) callback(subOrderList);
});
}
}
At CartItem[i].id it works fine and is able to work fine. But it fails at this line meal.mealQTY = CartItems[i].qty;
It can't recognize CartItems[i] inside the findOne() method.
Because findOne is async, i will always be CartItems.length inside the callback as the for loop runs to completion before any of the findOne callbacks occur.
You can fix this by iterating over CartItems using forEach instead so that each iteration's element is captured in a local function parameter:
function ParseOrderSchema(CartItems, callback) {
var lookup = 0;
var subOrderList = new Array();
CartItems.forEach(function(cartItem) {
Meal.findOne({ _id: cartItem.id }).lean().exec(function (err, meal) {
console.log(cartItem);
meal.mealQTY = cartItem.qty;
var s = new subOrder({ meals: meal, deliveryDate: getMomentDate(0) });
subOrderList.push(s);
if (++lookup == CartItems.length) callback(subOrderList);
});
});
}

MongoDB dynamic variables in MapReduce

I have node.js router for mongodb mapreduce:
app.get('/api/facets/:collection/:groupby', function(req, res) {
var collection = db.collection(req.params.collection);
var groupby = req.params.groupby;
var map = function() {
if (!this.region) {
return;
}
for (index in this.region) {
emit(this.region[index], 1);
}
}
var reduce = function(previous, current) {
var count = 0;
for (index in current) {
count += current[index];
}
return count;
}
var options = {out: groupby + '_facets'};
collection.mapReduce(map, reduce, options, function (err, collection) {
collection.find(function (err, cursor) {
cursor.toArray(function (err, results) {
res.send(results);
});
})
})
});
This works good. But I want to use my groupby param. When I try to do something like this:
var map = function() {
if (!this[groupby]) {
return;
}
for (index in this[groupby]) {
emit(this[groupby][index], 1);
}
}
I receive TypeError: Cannot call method 'find' of undefined. Is there any way to create such dynamic mapreduce function?
Thanks.
Edited:
Wow! I do it myself. Just pass scope param to mapreduce argument like so scope:{keys: groupby} and then I was able to do var key = this[keys] inside map function and use key variable instead this.region. Great!
Wow! I solved it myself. I just passed a scope param to the mapreduce argument.
scope:{keys: groupby}
Then I was able to do
var key = this[keys]
inside map function and use key variable instead of this.region. Great!

Resources