Dan Dan - 9 months ago 49
Javascript Question

MongoDB callback return issue for nested call

I am attempting to place a nested MongoDB call within an API I have been working on. The goal of the API is to update a persons goal based on exercise perform. The issue I currently have is trying to get the exercise events into the main function to enable me to compute "completeness".

I welcome any comments you may have on this issue.

Hello everyone,

I am attempting to place a nested MongoDB call within an API I have been working on.The goal of the API is to update a persons goal based on exercise perform.The issue I currently have is trying to get the exercise events into the main
function to enable me to compute "completeness".

I welcome any comments you may have on this issue.

module.exports.generateGoal = function(request, response) {

//User ID
var user_id = new ObjectId(request.params.id);

//Goal Search
Goals.find({
user_id: user_id,
active_goal: true
}, function(err, goals) {
if (err) {
//Error: send error to user
throw err
} else {
if (goals.length == 0) { //No goals, do nothing
return response.json({
success: true,
msg: "No Goal"
});
} else { //User as goals, therefore pull in exercise

for (i = 0; i < goals.length; i++) {
//Looking to have exercise available here for computation
queryExercise(user_id, goals[i].start_date, goals[i].end_date, function(result) {
//Exercise able to be accessed here
console.log(result)
return result
});
}
}
}
});
};

function queryExercise(user_id, start_date, end_date, callback) {
ExerciseData.find({
user_id: user_id,
exercise_date: {
$gte: start_date,
$lt: end_date
}
}, function(err, result) {
if (err) {
console.log(err);
} else if (result.length > 0) {
callback(result);
}
});
}


Edit 2:

I am very grateful for the response below which does exactly what I need. Thinking towards the future where any goal can have multiple exercises what would be the best method to employ to obtain an output similar to:

{
_id: dfhdjds8348hhj8
goal_id: 1
exercises: { {
exercise: 1,
type: running
},
{
exercise: 2,
type: running
}
}
}.
{
_id: ddhdjds8342hhj8
goal_id: 2
exercises: { {
exercise: 1,
type: jumping
},
{
exercise: 2,
type: rowing
}
}
}

Answer Source

You should be able to execute a single query using the aggregation framework pipeline i.e. use the $lookup, $unwind and $redact pipeline stages to return the documents you want via an aggregate() pipeline execution.

In the following example, the $lookup pipeline allows you to perform a "left join" to the other collection on the user_id field, flatten the array of documents produced as a result of the join with $unwind and then filter the documents in the pipeline with $redact which returns all documents that match the date condition using $$KEEP and discards otherwise using the $$PRUNE system variables:

module.exports.generateGoal = function(request, response) {

    //User ID 
    var user_id = new ObjectId(request.params.id);

    //Goal query
    Goals.aggregate([
        { "$match": { "user_id": user_id, "active_goal": true } },
        {
            "$lookup": {
                "from": "exercises",
                "localField": "user_id",
                "foreignField": "user_id",
                "as": "exercises"
            }
        },
        { "$unwind": "$exercises" },
        {
            "$redact": {
                "$cond": [
                    {
                        "$and": [
                            { "$gte": ["$exercises.exercise_date", "$start_date"] },
                            { "$lt": ["$exercises.exercise_date", "$end_date"] }
                        ]
                    },
                    "$$KEEP",
                    "$$PRUNE"
                ]
            }
        }
    ], function(err, goals) {
        if (err) {
            //Error: send error to user
            throw err
        } else {
            if (goals.length == 0) { //No goals, do nothing
                return response.json({
                    success: true,
                    msg: "No Goal"
                });
            } else { //User as goals + matching exercise
                console.log(goals);
                return goals
            }
        }
    });
};

Keep in mind that for each input document, $unwind outputs n documents where n is the number of array elements and can be zero for an empty array, hence there may be a need to group the documents again after the $redact pipeline as there will be potentially (n-x) documents per goal where n is the length of the exercises array produced after $lookup and x is the number of filtered out elements after $redact.