Mongoose recursive query parent reference - javascript

I am trying to do exactly what this mongo example is doing but in mongoose. It seems more complex to me in mongoose. Possibly i'm trying to fit a square peg in a round hole?
This example is from http://www.codeproject.com/Articles/521713/Storing-Tree-like-Hierarchy-Structures-With-MongoD (tree structure with parent reference)
I'm trying to build a path.
var path=[];
var item = db.categoriesPCO.findOne({_id:"Nokia"});
while (item.parent !== null) {
item=db.categoriesPCO.findOne({_id:item.parent});
path.push(item._id);
}
path.reverse().join(' / ');
Thanks!

Mongoose is an asynchronous library, so
db.categoriesPCO.findOne({_id:"Nokia"});
doesn't return the answer to the query, it just returns a Query object itself. In order to actually run the query, you'll need to either pass in a callback function to findOne() or run exec() on the Query object returned.
db.categoriesPCO.findOne({_id:"Nokia"}, function (err, item) {
});
However, you can't use the same while loop code to generate the path, so you'll need to use recursion instead. Something like this should work:
var path=[];
function addToPath(id, callback) {
db.categoriesPCO.findOne({_id:id}, function (err, item) {
if (err) {
return callback(err);
}
path.push(item._id);
if (item.parent !== null) {
addToPath(item.parent, callback);
}
else {
callback();
}
});
}
addToPath("Nokia", function (err) {
path.reverse().join(' / ');
});
NB In addition, instead of pushing new items onto the end of the path array and then reversing it, you could use path.unshift() which adds the item to the beginning of the array.

Related

Callback without effect, function still executed before callback

I have a function with a callback, my problem is that the return of the function happens before the callback ad then return null instead of returning me the array of coordinates
function callback(coordinates=[]){
console.log(coordinates);
return coordinates;
}
function getCoordinates(callback){
connection.connect();
let coordinates=[null];
connection.query('SELECT AreaId AS areaNumber, longitude AS longitude, latitude AS latitude FROM coordinates', function (error, results, fields) {
if (error) throw error;
let area=[];
for (result of results) {
if (!area.includes(result.areaNumber)){
area.push(result.areaNumber)
coordinates[result.areaNumber]=[];
}
coordinate=[result.longitude,result.latitude];
coordinates[result.areaNumber].push(coordinate);
}
coordinates=callback(coordinates)
});
connection.end();
return coordinates;
}
console.log(getCoordinates(callback));
and I have :
[null] //correspond to console.log(getCoordinates(callback));
and
[array with value] // corresponding to console.log(coordinates) in function vallback
How to do for that my callback will be considered?
Sorry I am kind of new on node.js so I may have missunderstood callback.
What I would like is to get an array of coordinates that I can use later on at 2 different places in my code like :
.get('/map', function(req, res) {
let Coords=getCoordinate(callback)
res.render('map.ejs', {token: tokenMapbox, coordinates: Coords});
})
.get('/wmap', function(req, res) {
let Coords=getCoordinate(callback)
res.render('wmap.ejs', {token: tokenMapbox, coordinates: Coords});
})
There are several approaches to this problem, however if you wish to use the resulting coordinates in a similar way to synchronous code, I suggest you try using the async/await syntax. If you call your query from an async function you can use the await keyword to provide more readable code.
Once you have your coordinates variable populated in your async function you can do what you wish with it.
For example:
function getCoordinates() {
connection.connect();
let coordinates = [];
return new Promise((resolve, reject) => {
connection.query('SELECT AreaId AS areaNumber, longitude AS longitude, latitude AS latitude FROM coordinates', function (error, results, fields) {
if (error) {
reject(error);
} else {
let area=[];
for (result of results) {
if (!area.includes(result.areaNumber)){
area.push(result.areaNumber)
coordinates[result.areaNumber]=[];
}
coordinate=[result.longitude,result.latitude];
coordinates[result.areaNumber].push(coordinate);
}
resolve(coordinates);
connection.end();
}
});
})
}
async function testCoordinates() {
let coordinates = await getCoordinates();
console.log("Coordinates:", coordinates);
// You can do whatever you wish with the coordinates variable
}
testCoordinates();
You're confusing different things. Let me explain
First thing, forget about the term "callback". We'll understand this simply as a function passsed as parameter to another function. Don't worry about it, I'm going to explain.
So we start from the problem: how to fetch array of coordinates from database and print it
Next thing you get to know about interacting with database and assuming this is how your library works: it has a function connection.query(myQuery, someFunction) which can get you results from database.
Now first thing you notice about that query function is it's parameters. myQuery is a string and someFunction is a function definition. While we have seen in other languages that we pass values such as numbers, string, etc. as parameters to a function, interestingly in javascript you can pass on a function definition as a parameter as well. And this is what makes javascript powerful, you can pass on function definition as parameter.
Wow, passing on function as parameter, but how does that work?
Let's take a different example; let's say here I want to create a function which will do some calculations
//Here myVar is a variable and doSomething is a function
function interestingJsFunction(myVar, doSomething){
var twiceOfVar = 2*myVar;
doSomething(twiceOfVar);
var thriceOfVar = 3*myVar;
doSomething(thriceOfVar);
}
So what does this function do? It applies some calculations and calls the function doSomething at some points; at one point it passes twice value of myVar to function and at one point it passes thrice of myVar. But what this function doSomething do is not defined as of now. And you can define it by passsing your own function as parameter. This gives you infinite possibilities. How?
interestingJsFunction(2, function(result){ console.log(result) })
interestingJsFunction(2, function(result){ console.log("the new behaviour" + result) })
//And so on...
You understand how this has made one interestingJsFunction useful in different cases. If you knew that you would need only one implementation of doSomething(), you could have simply removed the doSomething from parameters and whatever you wanted to do, you could have done that directly inside interestingJsFunction(e.g. console.log(twiceOfVar))
Now coming back to the query function. It is also an interestingJsFunction and if you go to definition of query function(you can dig up the code of your library to see what's inside query function) you'll find that it does some operations on database, gets the results and call the function[similar to doSomething] which was passed as parameter, if it gets any error while doing so, it calls the function in parameter and passes error to it.
When it does so, it sends back error, results and field as parameter to this function. So now you can utilize these paramaeters in your function which was passed as parameter. It's upto you now how you want to utilize this, it could be that you can simply print the results as following (remember how we used interestingJsFunction?)
//We could simply print the results if there are any
connection.query(myQuery, function(error, results, fields){
if(results) console.log(results.toString())
})
//Or we could throw erorr if there's any
connection.query(myQuery, function(error, results, fields){
if(error) throw error
})
//Guess what would happen when you return something inside this function?
var myQueryFunction = connection.query(myQuery, function(error, results, fields){
return "return of query function"
})
//And guess what would happen when you have something like this
function myNewFunction(){
var x = connection.query(myQuery, function(error, results, fields){
return "return of query function"
})
return "return of myNewFunction"
}
I leave last 2 exercises for you and you should be able to fix the problems after that. Try console.log statements to understand

MongoDB + node.js - SQL Select equivalent

I'm pretty new to node.js and mongodb. I need a function, that will return an array with results from database. When I query 'SELECT * FROM table' in php, it returns array or array of objects. But with node and mongo I have to use lot of code, which seems so unnecessary to me. So I wrote this function:
select: function (table, terms) {
var rows = [];
var find = function (error, db) {
var collection = db.collection(table);
var docs = collection.find(terms);
docs.each(function (error, doc) {
rows.push(doc);
});
};
client.connect(url, find);
return rows;
}
It takes table name [string] and terms [js object] as arguments. And I want to this function return an array like this: [ { doc1 }, { doc2 }, { doc3 }, ...].
When I log variable doc, in each function, it's alright - valid javascript object. But when I log variable rows after pushing all docs, it's empty - []. Also the result of function select() is empty array - [].
Please, is there someone who has an idea about what could be a problem or who uses some similar method to fetch data from MongoDB? Thank you
The connect function is asynchronous, and finishes after your return statement. Put your return statement within the callback function, just after the closing bracket of docs.each(.....); This way you're sure your return statement will be executed after your array has been filled.
Edit for completeness: a return statement doesn't work of course, because it returns from the function in find. Work with a callback function.
Like this:
select: function (table, terms, cb) {
var rows = [];
var find = function (error, db) {
var collection = db.collection(table);
var docs = collection.find(terms);
docs.each(function (error, doc) {
rows.push(doc);
});
cb(error, rows);
};
client.connect(url, find);
}
Edit: Above example (cb function instead of return)

NodeJs: Filter mongo query results with results from another collection

I have a situation where I need to perform logic on a distinct set of values from a mongo collection (A) and then save result to another collection (B). However the contents of (A) will change over time and so I only want to perform the logic on those documents in (A) where there is not a corresponding document in (B). As joins aren't supported, I am trying to do this at the Node level. I am querying all items in collection (A) and using findOne to look for the corresponding entry in collection (B). If I find it, I would like to remove it from the array, but I am stuck because findOne uses an asynchronous callback which doesn't seem to work with the array filter method. Is there a better way to do this:
function loadNewDocumentsFromDB(callback){
db.collection('A', function(err, acollection){
bcollection.find().toArray(function(err, arr){
if(arr){
// toQuery is the global array to be used elsewhere
toQuery = arr.map(function(config){
transformed =
{
args: config._id, // these args are a doc representing a unique entry in 'B'
listings: config.value.id.split(',') // used by other functions
};
return transformed;
})
.filter(function(transformed){
db.collection('B', function(err, bcollection){
bcollection.findOne(transformed.args, function(err, doc){
// I want these values returned from the filter function not the callback
if(doc){
return false; // want to remove this from list of toQuery
}else{
return true; // want to keep in my list
});
});
}
callback();
});
});
}
This was how I managed to get it working:
function loadOptionsFromDB(callback){
toQuery = [];
db.collection('list', function(err, list){
db.collection('data', function(err, data){
list.find().each(function(err, doc){
if(doc){
transformed =
{
args: doc._id,
listings: doc.value.id.split(',')
};
(function(obj){
data.findOne(obj.args, function(err, found){
if(found){}
else{
toQuery.push(obj);
}
});
})(transformed);
}else{
//Done finding
setTimeout(callback, 20000);
}
});
});
});
}
A better way would be to do this on the database. Check if 2 executions of http://docs.mongodb.org/manual/core/map-reduce/ would be of any use to you.
See Merging two collections in MongoDB for more information

javascript express js passing async resuls

I'm new to js.
I am using express for node js, and mongoose as a mongo orm.
function direct_tags_search_in_db(tags){
var final_results = [];
for (var i=0; i<tags.length; ++i) {
var tag = tags[i];
Question.find({tags: tag}).exec(function(err, questions) {
final_results.push(questions);
if (i == tags.length -1 ){
return final_results;
}
});
}
};
I get empty results, because of the asynchronously of the find. But I don't know what the best approach for this.
Appriciate a little help, thanks.
You will often find that methods such as Question.find().exec that accept a function as an argument are async. It is especially common for methods that perform network requests or file system operations. These are most commonly referred to as a callback. That being the case, if you would like something to occur when the async task(s) complete, you need to also implement a callback.
Also, it is possible that your reference to tag is being changed in a way that is likely undesired. There are a number of solutions, here is a simple one.
function direct_tags_search_in_db(tags, callback){
var final_results = [];
// Array.forEach is able to retain the appropriate `tag` reference
tags.forEach(function(tag){
Question.find({tags: tag}).exec(function(err, questions) {
// We should be making sure to handle errors
if (err) {
// Return errors to the requester
callback(err);
} else {
final_results.push(questions);
if (i == tags.length -1 ){
// All done, return the results
callback(null, final_results);
}
}
});
});
};
You will notice that when we implement our own callback, that we follow the same common pattern as the callback for Question.find().exec(function(err, result){}); -- first argument a potential error, second argument the result. That is why when we return the results, we provide null as the first argument callback(null, final_results);
Quick example of calling this function:
direct_tags_search_in_db([1, 2, 3], function(err, results){
if (err) {
console.error('Error!');
console.error(err);
} else {
console.log('Final results');
console.log(results);
}
});
Another option for solving various async goals is the async module, promises, or otherwise.

What's the proper way of chaining async functions in Node.js?

I have an interesting case where I need to do a few queries in MongoDB using Mongoose, but the response is returning before I can complete all of them.
I have two document types, list and item. In one particular call, I need to get all of the lists for a particular user, then iterate over each of them and fetch all of the items and append them to the appropriate list before returning.
List.find({'user_id': req.params.user_id}, function(err, docs){
if (!err) {
if (docs) {
var results = [];
_und.each(docs, function(value, key) {
var list = value.toObject();
list.items = [];
Item.find({'list_id': value._id}, function(err, docs) {
if (!err) {
_und.each(docs, function(value, key) { list.items.push(value.toObject()); });
results.push(list);
}
else {
console.log(err);
}
});
});
res.send(results);
(_und is how I've imported underscore.js)
Obviously the issue are the callbacks, and since there's multiple loops I can't return within a callback.
Perhaps this is a case where I would need to get the count in advance and check it on every iteration to decide when to return the results. This doesn't seem elegant though.
Code solution
First of all the issue is with the code. Your sending the results before the Item.find queries finish. You can fix this quite easily
var count = docs.length + 1;
next()
_und.each(docs, function(value, key) {
var list = value.toObject();
list.items = [];
Item.find({
'list_id': value._id
}, function(err, docs) {
if (!err) {
_und.each(docs, function(value, key) {
list.items.push(value.toObject());
});
// push asynchronous
results.push(list);
next()
}
else {
console.log(err);
}
});
});
function next() {
--count === 0 && finish()
}
function finish() {
res.send(results)
}​
The easiest way is reference counting, you default count to the number of documents. Then every time your finished getting an item you call next and decrement the count by one.
Once your finished getting all items your count should be zero. Note that we do .length + 1 and call next immediately. This gaurds against the the case where there are no documents, which would otherwise do nothing.
Database solution
The best solution is to use mongo correctly. You should not be doing what is effectively a join in your code, it's slow and inefficient as hell. You should have a nested document and denormalize your list.
so list.items = [Item, Item, ...]
As a further aside, avoid mongoose, it's inefficient, use the native mongo driver.
I use with this module:
https://github.com/caolan/async

Categories

Resources