0

I'm using boot scripts to create some models and relations. For example I might have this:

+-------+                +----------+
| Order | --belongsTo--> | Customer |
+-------+                +----------+

and I'd like to create: 1 Customer, and 1 Order belonging to that Customer.

I know that loopback-boot executes the scripts in server/boot in filename alphabetical order, so I have the following boot scripts:

// 0-create-customer.js
module.exports = function(app) {
    app.dataSources.mongoDs.autoupdate('Customer', function(err) {
        if (err) throw err;
        var obj = {name: 'Bob'};
        app.models.Customer.findOrCreate({where: obj}, obj
        , function(err, customer) {
            if (err) throw err;
        });
    });
};

and for the Order, I find the Customer first and create the order with customer.id:

// 1-create-order.js
module.exports = function(app) {
    app.dataSources.mongoDs.autoupdate('Order', function(err) {
        if (err) throw err;
        app.models.Customer.findOne({where: {name: 'Bob'}}
        , function(err, customer) {
            if (err) throw err;
            var obj = {customerId: customer.id, amount: 42};
            app.models.Order.findOrCreate({where: obj}, obj
            , function(err, order) {
                if (err) throw err;
            });
        });
    });
};

The problem is, it seems the boot scripts do not wait until the models are created before exiting, therefore sometimes I run into these errors in the second script:

TypeError: Cannot read property 'id' of null

referring to this line:

            var obj = {customerId: customer.id, amount: 42};
                                   ^

I'd rather not add a small wait before creating the Order since that seems flaky and won't guarantee the parent model's existence especially if the data source happens to be slow.

I'd also rather not have to combine all this code into a single file because my real project has lots of models and this will result in a huge unmaintainable file.

Is there a good way to wait for the parent model auto-migration to complete before starting on the child?

congusbongus
  • 13,359
  • 7
  • 71
  • 99

2 Answers2

4

You can create async boot scripts using an extra callback param and calling it when the script is ready.

Example:

module.exports = function (app, cb) {
  var db = app.dataSources.db;

  // update all database models
  db.autoupdate(function (err) {
    if (err) throw err;
    cb();
  });
};
TiagoLr
  • 2,782
  • 22
  • 16
  • I saw this in the [documentation](https://docs.strongloop.com/display/public/LB/Defining+boot+scripts#Definingbootscripts-Asynchronousbootscripts) but I don't understand what the callback does. Does it block execution of other boot scripts until the callback is called? – congusbongus Feb 25 '16 at 04:11
  • Yes, thats what it does. – TiagoLr Feb 25 '16 at 04:17
0

One way is to keep looping until the Customer has been found in the data source.

Therefore the Order creation script might look like this:

// 1-create-order.js
module.exports = function(app) {
    app.dataSources.mongoDs.autoupdate('Order', function(err) {
        if (err) throw err;
        var customerId = null;
        function addOrder(customerName, obj) {
            if (customerId === null) {
                app.models.Customer.findOne(
                    {where: {name: customerName}}, function(err, customer) {
                    if (err) throw err;
                    if (customer !== null) {
                        customerId = customer.id;
                    }
                });
                setTimeout(addOrder, 1000, customerName, obj);
                return;
            }

            obj.customerId = customerId;
            app.models.Order.findOrCreate({where: obj}, obj
            , function(err, order) {
                if (err) throw err;
            });
        }
        addOrder('Bob', {amount: 42});
    });
}

So the function addOrder will keep calling itself using setTimeout until the Customer has been created and found in the database, where it will be used to create the Order.

Community
  • 1
  • 1
congusbongus
  • 13,359
  • 7
  • 71
  • 99
  • Please don't use such method. If your bootscript is async it must have the async `(app,cb)` signature, and it will also make your code and life easier. – Overdrivr Feb 25 '16 at 07:23