3

I am new to Mongo and Node. I am currently using Mongoskin and Bluebird to handle the db connection and queries (as suggested here: https://stackoverflow.com/a/23687958/2701348 ).

I have three collections: Users, Binders and Cards.

The Binders collection contains the information about Cards for each User. Each document in Binders has as properties:

User Id <--- that refers to the User owning the Card
Card Code <--- that refers to a Card
Count <--- that refers to the number of cards owned by the User

I prefer to have a separate Cards collection so that when a Card changes, it changes for all the Users Binders.

Now I am willing to retrieve an array for a given user such as:

[{card: {card document}, count: 4}, ...]

I have the following problems:

  • the db connection should be closed after all the async db callbacks are called
  • the cards array should be returned after the last db.collection('cards').find gives back the results

I know my following code is wrong but can be a starting point for a discussion:

var getAllBinderCards = function(req, res){
    var db = req.db;
    var userId = req.userId;

    var promiseBinders = db.collection('binders').find({userId: userId}).toArrayAsync();

    promiseBinders.then(function(binderCards) {
        if (binderCards) {
            var promiseCards;
            //console.log("------ binderCards: ", binderCards);
            var cards = [];
            for (var i = 0; i < binderCards.length; i++) {
                var binderCard = binderCards[i];

                promiseCards = db.collection('cards').find({Code: binderCard.Code}).toArrayAsync();
                promiseCards.then(function(cardsDB){
                    if(cardsDB){
                        //console.log("Cards found: ",binderCard.Code, cardsDB);
                        for (var i = 0; i < cardsDB.length; i++) {
                            cardsDB[i].count = binderCard.count;
                        };
                        cards.concat(cardsDB);
                    }
                }); 
            }

            promiseCards.then(function(){
                db.close();
                console.log("Binder Cards: " , cards);
                res.json(cards);
            });
        }
    });
}

I am struggling trying to figure out how to handle the promisfied asynchronous call correctly in order to send back the whole array and close the db connection.

I think I should try to build a promise before the for loop and use it to chain the query on Cards promises and lastly chain the db.close() and res.json(cards) statements.

[EDIT] Maybe the easiest solution is to simply use the $in filter inside a single db.collection('cards').find({Code: {$in: [bindersCodeArray] }}).toArrayAsync(); and avoid that for loop:

var getAllBinderCards = function(req, res){
    var db = req.db;
    var userId = req.userId;

    var promiseBinders = db.collection('binders').find({userId: userId}).toArrayAsync();

    promiseBinders.then(function(binderCards) {
        if (binderCards) {
            var binderCodes = binderCards.map(function(element){
                return element.Code;
            });
            var promiseCards = db.collection('cards').find({Code: {$in: binderCodes} }).toArrayAsync();
            promiseCards.then(function(cards){

                var bindersDictionary = {};

                for (var i = 0; i < binderCards.length; i++) {
                    bindersDictionary[binderCards[i].Code] = binderCards[i].count;
                };

                for (var i = 0; i < cards.length; i++) {
                    cards[i].count = bindersDictionary[cards[i].Code];
                };

                db.close();
                console.log("Binder Cards: " , cards);
                res.json(cards);
            });

        }
    });
}

Still I am curious if there is an elegant way to solve this riddle using promises.

Community
  • 1
  • 1
Niko Zarzani
  • 1,372
  • 2
  • 15
  • 28

1 Answers1

1

I would expect that using $in and array may have constraints on the number of binders you can pass and affect query performance. You can also try doing this with async#map. e.g.:

...

function(binders) {
  async.map(binders, cardsForBinders, function(err, bindersWithCards) {
    // TODO: close connection here.
  }

  function cardsForBinders(binder, callback) {
    // 1. find cards for binder.
    // 2. prepare transformed response: binderWithCards.
    callback(null, binderWithCards);
  }
}

...
Alex V
  • 1,155
  • 8
  • 10