Loopback: Other model not available for User.on('attached'...

Created on 27 Jul 2014  路  16Comments  路  Source: strongloop/loopback

(LoopBack 2.0.1)

In my common/models/user.js file, I want to access a settings table to retrieve SMTP parameters. As per the docs:

module.exports = function(User) {

  User.on('attached', function() {
    var app = User.app,
      models = app.models

console.log(models)

   var Settings = models.Settings  // Settings is null

The Settings property is absent from app.models, as confirmed by the console log. The only models in app.models are "user" and "User".

If I wrap the access to models.Settings in a setTimeout(fn, 5000), it works. So the app object is available when the 'attached' event is fired but not all models are available.

Most helpful comment

Then the doco is misleading. It implies that .on('attached'... will ensure access to other models.

If your model's setup requires the application object (or other models), you have to defer it until the model was added to the application.

Is there any way to wait on all models attached?

All 16 comments

It should be settings instead of Settings.

Raymond,

True, it should be settings, but Settings does actually work after a timeout. I have other lowercase models where app.models.Table works as well as app.models.table.

Anyway, I tried with settingsand I still get:

TypeError: Cannot call method 'findOne' of undefined

when I call settings.findOne()

When a model is added to app, the attached event is emitted. Depending on the order of models being registered, your on listener may be called before Settings model is attached.

Then the doco is misleading. It implies that .on('attached'... will ensure access to other models.

If your model's setup requires the application object (or other models), you have to defer it until the model was added to the application.

Is there any way to wait on all models attached?

@johnsoftek how did you solve this?

@ericprieto A bit hacky... I guess it is possible that both models are attached when the first model js is called, so you may have to take steps to ensure the action takes place only once.

In settings.js:

  Settings.on('attached', function() {
    var app = Settings.app,
      User = app.models.user
    if (User) {
      setup_user_smtp_transport()
    }
  })

In user.js:

  User.on('attached', function () {
    var app = User.app,
      Settings = app.models.settings
    if (Settings) {
      Settings.setup_user_smtp_transport()
    }
  }

@raymondfeng I would like to suggest a boot event or something similar, which is emitted once all models have loaded, right before any bootscripts are called. I did this in my own apps already, because it's easy to trip over the load-order issue when you have many models.

@raymondfeng I used loopback.getModel('User') instead of app.models.User. I think It works indepentent of load order.

@ericprieto You can add a bootscript such as /server/boot/setup-user-model.js to further set up the model. The boot script will receive the app instance. Accessing other models in the model def js is too early.

@raymondfeng I would still like an event for this - boot scripts decouple from the logic that usually defined in models/*.(js|json) files, I think.

@fabien: would you mind sharing how you achieve this kind of boot event or your own?

@raymondfeng : other than that, i see there is a "started" event which emits when all boot scripts are executed (and hence every models are loaded), can it be considered as the implementation of what Fabien is refering to ?

This helped me fixing the problem!

module.exports = function(Model, options) {
  var app = require('../../server/server');

  var AnotherModel = app.loopback.getModel("AnotherModel");
  AnotherModel.on("attached", function(){
    //AnotherModel is initialized
  });

};

@jesperbruunhansen as a best practice i usually use this to declare only once other models per model.js file:

var app = require('../../server.js');
var models = app.models;

var AnotherModel;

app.on('started', function () {
    AnotherModel     = models.AnotherModel;
});

the started event is broadcasted only once the app has finished loading (including attaching any registered models)
It really helps keeping the code clean

Thanks @ebarault, I wasn't aware of the started event - I will definitely look into your method!

just a warning though @charlie-s : by doing loopback.getModel() you are using loopback's global registry. This might lead to unexpected results if you set up the app to use local registries.

Does anyone have a 100% surefire solution for this?
UnhandledPromiseRejectionWarning: error: relation "_aModel.aRelation_" does not exist
I have tried all of the above solutions without success. I have tried app events _booted_ and _started_. I have tried all of the above solutions. I have tried nesting multiple once attached. I want an event that tells me when automigrate/autoupdate has completed and ALL models are attached to their datasources and ready to be used.

Was this page helpful?
0 / 5 - 0 ratings