0

I'm wondering what the best approach would be to realize the following situation in Node.js and mongoose:

I'm having a collection with users and a collection with groups. I want users to be able to create new groups and add people to them (very similar to people creating a new whatsapp group). I use the following schemas for the user and group documents:

var userSchema = mongoose.Schema({ 
    email: String, 
    hashed_password: String,
    salt: String,
    name: String,
    groups: [{
        _id: { type: Schema.Types.ObjectId, required: true, ref: 'groups' }, 
        name: String
    }]
});

var groupSchema= mongoose.Schema({ 
    name: String,
    creator: Schema.Types.ObjectId,
    users: [{
        _id: { type: Schema.Types.ObjectId, required: true, ref: 'users' }, 
        rankInGroup: { type: Number, required: true }
    }]
});

At this moment I have a function that takes the following arguments: the email address (groupCreator) of the user who is creating the group, the name of the new group (newGroupName), and the userids of the users (newGroupMembers) that need to be added to the group. This function first finds the users that need to be added to the group (using this method) and then adds the user ids of these users to the users array in the group document like this:

function(groupCreator, newGroupName, newGroupMembers , callback) {
    userModel.findOne({
        email: emailGroupCreator
    }, function(err,creator){
        //load document of user who creates the group into creator variable
        var newGroup = new groupModel({ 
            name: newGroupName, 
            creator: creator._id, 
            users: [{_id: creator._id, rank: 0}] //add group creator to the group
            });

        userModel.find({
            email: { $in: newGroupMembers }
        }, function(err,users){                         
            //for now assume no error occurred fetching users
            users.forEach(function(user) {                              
                newGroup.users.push({_id: user._id, rank: 0}); //add user to group
                user.groups.push({_id:newGroup._id,name:newGroup.name}); //add group to user
            }
            newGroup.save(function (err) {
            //for now assume no error occurred
            creator.groups.push({_id: newGroup._id, name: newGroup.name}); //add group to creator
            creator.save(function (err) {
                //for now assume no error occurred

                /* save/update the user documents here by doing something like
                newMembers.forEach(function(newMember) {
                    newMember.save(function (err) {
                        if(err){
                            callback(500, {success: false, response: "Group created but failure while adding group to user: "+newMember.email});
                        }
                    });
                });
                callback(200, {success: true, response: "Group created successfully!"});
                */ 
            });
        }
    });
}

So I want this function to:

  • Find the user document of the group creator based on its email address stored in groupCreator
  • Create a new group (newGroup) and add the creator to its users array
  • Find all the users that need to be added to the group
  • Update the groups array in all the user documents with the groupid (newGroup._id) of the newly created group
  • Make a callback if all this is successfully executed

The problem here is that the updating of all the user documents of the users added to the group happens asynchronously, but I want to be sure that all the user documents are updated correctly before I return a success or failure callback. How can I update all the user documents before I continue with the rest of the code (maybe not using a foreach)? Is my initial approach of retrieving all the user documents good or are there better ways to do this?

So the bottom line question is; how can I save multiple user documents and continue with the rest of the code (send a callback to notify success or failure) after all the save actions are performed, or is there a way to save all the documents at once?

NB The reason why I want (some) of the same information in both the user and the group document is because I don't want to load all the group info for a user if he logs in, only the basic group information. See also this source under the section many-to-many relationships.

Community
  • 1
  • 1
Koningh
  • 628
  • 2
  • 8
  • 22
  • 2
    Basics here is that JavaScript `.forEach()` cannot respect async callbacks. But it is basically unclear in your question where your data ( variables referenced that are not defined in the listed code ) is coming from or the full intent of your update. You can make these points clearer by including the other relevant actions in your question and explaining an expected result from a source data sample. – Blakes Seven Oct 15 '15 at 12:56
  • I've added some more information and a list of what this function should do :) – Koningh Oct 15 '15 at 13:09
  • 1
    You can use an async iterator like [`async.each`](https://github.com/caolan/async#each) to do this sort of thing. – JohnnyHK Oct 15 '15 at 14:10
  • 1
    Hmm that looks interesting, I just stumbled upon this page (http://www.sebastianseilund.com/nodejs-async-in-practice) referring to async.parallel. Will look into that! – Koningh Oct 15 '15 at 14:30

1 Answers1

1

JohnnyHK pointed me in the right direction; async.each make it possible to iterate over the documents and update them one at a time. After that the rest of the code gets executed. This is how my code looks now:

async.each(groupMembersDocs, function (newMember, loopCallback) {
    //console.log('Processing user ' + newMember.email + '\n');
    userModel.update({
        email: newMember.email
    }, {
        $push: { 'groups' : {_id: newGroup._id, name: newGroup.name} }
    }, function (err, data) { 
        if(err){
            console.log("error: "+err);
            loopCallback('Failure.. :'+err);
        } else{
            newGroup.users.push({_id: newMember._id, rank: -1});
            loopCallback();
        }
    });
}, function (err){
    if(err){
        console.log("error: "+err);
        callback(500, {success: false, response: "Error while adding users to group"});
    } else{
        newGroup.save(function (err) {
            callback(201, {success: true, response: "Group created successfully"});
        });
    }
})
Community
  • 1
  • 1
Koningh
  • 628
  • 2
  • 8
  • 22