0
votes

I've a Parent and Child models in my app. Parent.create receives parent_name and an array of children that I want to add to the Parent model, the following flow describes the function:

1) Create parent object

2) Create all children

3) Save parent with updated children array

The problem is that Parent.create is probably async, and the 'created_children' array when saved to parent is empty (because it doesn't wait until the Parent.create finishes.

How can I make Model.create dependent (or synchronic)?

See the code below (I commented the buggy part //BUG: EMPTY ARRAY!!!!!!!!!!):

create: function(req, res, next) {

    var childrenInput = req.param('children');
    var parentObj = {
        name: req.param('parent_name')
    };

    Parent.create(parentObj, function parentCreated(err, parent) {

        if (err) {
            return res.redirect('/parent/new');
        }

        // assign children
        var created_children = new Array();
        for(var i=0; i < childrenInput.length; i++) {

            var childObj = {
                name: parentObj.childrenInput[i],
                parent_id: parent.id
            };

            // create child
            Child.create(childObj, function childCreated(err, child) {

                if (err) {

                    for(var j=0; j < created_children.length; j++) {
                        Child.destroy(created_children[j].id, function childDestroyed(err) {
                            if (err)
                            {
                                // BIG ERROR
                                return next(err);
                            }
                        });
                    }
                    return res.redirect('/parent/new');
                }
                // add created child
                created_children.push(child.id);
            }) // end of Child.create;
        } // end of for;

        // save created children to parent
        parent.children = created_children.slice();
        parent.save(function(err, c) {
            if (err)
            {
            // TODO: FUNCTION TO DESTROY ALL CHILDREN
                return next(err);
            }
        });

        return res.redirect('/parent/show/' + parent.id);
    });
},

Parent model

module.exports = {

schema: true,

attributes: {

    name: {
        type: 'string',
        required: true,
        unique: true
    },

    children: {
        type: 'array',
        defaultsTo: []
    }
}

};
1

1 Answers

1
votes

Performing asynchronous operations on an array can be a real pain. I'd suggest using a module like async, which provides synchronous-like functionality for asynchronous code. You could then rewrite your code as:

Parent.create(parentObj, function parentCreated(err, parent) {

    if (err) {
        return res.redirect('/parent/new');
    }

    // You only really need this for error handling...
    var created_children_ids = new Array();

    // Create an array of child instances from the array of child data
    async.map(

        // Array to iterate over
        childrenInput, 

        // Iterator function
        function(childObj, callback) {
            Child.create(childObj, function childCreated(err, child) {
                if (err) {return callback(err);}
                created_children_ids.push(child.id);
                // 'null' indicates no error
                return callback(null, child);
            });
        },

        // Callback for when loop is finished.
        // If any run of the iterator function resulted in the
        // callback being called with an error, it will immediately
        // exit the loop and call this function.  Otherwise the function
        // is called when the loop is finished, and "results" contains
        // the result of the mapping operation
        function (err, results) {
             if (err) {return destroyChildren();}
             // Save the children to the parent
             parent.children = results;
             parent.save(function(err, c) {
                 if (err) {return destroyChildren();}
                 return res.redirect('/parent/show/' + parent.id);                 
            });

            function destroyChildren(err) {
                Child.destroy({id: created_children_ids}).exec(function() {
                   // Respond with an error
                   return res.serverError(err);
                });
            }
        }
    );
});

Note that if you're using Sails v0.10, you can use actual associations to bind the parent and child records, and use parent.children.add(childObj) (which is a synchronous operation) in a regular loop prior to calling parent.save(). Calling .add with an object will cause that model to be created during the save operation.