Multiple models not loaded on a route

This question is on stackoverflow.

I have a model called ‘Scenario’ that is loaded via a RESTAdapters with a hasMany relationship with a ‘Event’ model.

On the ‘scenario.index’ route I need only the scenarios, but on the ‘scenario’ route I need a list of all the events so I can present a form to associate them with the scenario shown.

This works if I access directly the ‘scenario’ route, but not if I first access the ‘scenario.index’ route and then the ‘scenario’ route.

I think this happen because the ‘Scenario’ model is already loaded and the model function doesn’t get called again but I don’t know how to solve this problem.

Any idea?

App.Router.map(function()
{
    this.resource('scenarios', function()
    {
        this.resource('scenario', {path: '/:id'});
    });
});

App.ScenariosIndexRoute = Ember.Route.extend({
    model: function()
    {
        return this.store.find('scenario');
    }
});

App.ScenarioRoute = Ember.Route.extend({
    model: function(params)
    {
        return {
            scenario: this.store.find('scenario', params.id),
            events: this.store.find('event')
        };
    }
});

Can you see what requests are being made to your data store? If the relationship is async: true (you don’t say if it is) the related models won’t be loaded until they’re explicitly asked for. (I’m not certain that’s the problem, it’s just what I’d check first.)

Yes, all my relationships are async but if I do a console.log in ScenarioRoute.model() and ScenarioRoute.beforeModel() and I access directly ScenarioRoute, both methods get called. If I access ScenarioRoute after ScenarioIndexRoute, only beforeModel() gets called.

Hi :smile: This might help you:

Thank you, I’ll try this and let you know.

Ok, this is working but on the thread that you linked @ulisesrmzroche says:

Setup controller is not promise aware, and it is thus called after the model hooks have been resolved. You won’ t be able to handle errors gracefully, and won’t be sure you have the right data at the right time, opening the doors to all kinds of timing-bugs.

Will I face other problems if I use setupController to get models?

Hi @papagno, yes he’s right. You shouldn’t use setupController because it’s not promise aware. So, you should use the afterModel hook to load the data you need. I haven’t done this yet myself, but I understand the theory, so I can explain it, and hopefully you can try it and/or if I get it wrong, others can chime in.

It uses similar semantics to the beforeModel hook.

so:

assuming you want to set the allCountries value on the posts controller:

App.PostRoute = Ember.Route.extend({
  afterModel: function(transition) {
    var self = this;
    var postsController = this.controllerFor('posts');
    if(Ember.isEmpty(postsController.get('allCountries'))) {
      // find returns a promise. We attach a "then" "callback" to it
      // so that "callback" will execute after the store's find promise has resolved
      self.store.find('country').then(function(countries) {
        postsController.set('allCountries', countries)
      });
    }
  }
});

Note that there’s no error-handling stuff there, but at least you won’t have the timing-dependent issues that were being discussed (ie the router will only transition AFTER this has resolved). (Having said that, I don’t know what will happen if it doesn’t resolve… I wonder if it’ll just hang indefinitely if your backstore fails on the request, for example…) It’s probaby worth us getting @ulisesrmzroche to chime in here, or take a look (?) :smile:

Take this with a grain of salt - it might not be the “right” way to do this. It’s only my first guess.

I’ll try this code asap.

sweet, so yeah, similar code. beforeModel and afterModel deal with the problem of the model hook not being called a second time.

I’m not convinced using it as a way to instantiate has and belongs relationships though is best though. I gotta make a energy drink real quick but I’ll be right back.

@ulisesrmzroche absolutely, you’re bang on.

@papagno This is how I’d tackle this particular use case:

  App.ScenarioRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('scenario', params.id).then(function(scenario) {
      scenario.get('events);
    });
  }
});

That is, you’re a promise with another promise chained: store.find returns a promise, which we then use a “then” call on to get the events from the scenario once it has been obtained.

Does this make sense?

The thing is now you’re not returning the scenario, but the events attached to it. ScenarioRoute should be a 1:1 with Scenario Model.

Check this guide out, it talks about needs. http://emberjs.com/guides/controllers/dependencies-between-controllers/

Hey @papagno, is that actual code? Why is one called ScenariosIndexRoute, and the other is ScenarioRoute? They should be ScenarioIndexRoute and ScenarioRoute, both singular.

Sorry of course you’re right. My brain just dropped its contents. :wink:

This is the correct version, I hope! :slight_smile: :

App.ScenarioRoute = Ember.Route.extend({
  model: function(params) {
    var scenario = this.store.find('scenario', params.id);
    scenario.then(function(scenario) {
      scenario.get('events)
    });
    return scenario;
  }
});

Or maybe THIS is the best way to do it?

model: function(params) {
  new Ember.RSVP.Promise(function(resolve, reject) {
    this.store.find('scenario', params.id).then(function(scenario) {
      scenario.get('events').then(function(events) {
        resolve(scenario);
      });
    });
  });
}

Sadly, I just don’t know.

Yeah, see when things are getting tricky, it’s a sign you strayed off the happy path.

Look at the OP code, do you see how Scenario and Scenarios are different? This actually matters a lot because of the Ember Resolver. Also, the code in Scenario Index is actually returning an object that contains promises, not a promise.

(THIS LINE IS JUST HERE TO STOP discuss BEING STUPID AND NOT LETTING ME POST “too similar” posts as edits)

What is the happy path, though? :slight_smile: It’s definitely not controller “needs” here, it seems. He just wants to make sure the events are loaded when he loads his individual scenario, right? It should be easy and simple to do something like that, surely?

Yeah, the route object name for scenario is wrong, I think? it should be App.ScenariosScenarioRoute shouldn’t it? Man, routes confuse the heck out of me at the best of times. The doc on them is just utterly confusing.

This largely depends on what OP’s intent is, really, doesn’t it…

My impression of what he’s intending to do is this:

  1. going to /scenarios loads a big list of scenarios, which link to scenario/:id each.
  2. clicking on a scenario, therefore, paths to ‘/scenarios/5’ for the one with id 5.
  3. the path /scenarios/5 will then load scenario 5, and also load its associated events
  4. then, the page should display… and display a form related to adding and removing events (i’m guessing)

What’s the happy path for this? Is it to have two sets of nested resources? Note that he doesn’t want to use the intervening index paths.

As he has it, having a resource called scenarios, then a resource called scenario makes little sense to me. We really need to work out his intent.

Perhaps me building a series of learning tools will help me to actually understand how this works, too, because I’d be forced to build out the common use cases that I want to teach, then I’d probably need to discover the best way to do these things. :smile: I’m quite prepared to put time and energy into this… if it seems there’s an audience. Also I think it’s possible to monetize it while still being free for everyone who wants it to be free.

Thank you all for the replies but the events that I need to load for ScenarioRoute are not the events in the hasMany relationship. I need to load ALL the events in the store to present to the user a select to add events to the scenario.

That is real code and it’s working for me.

So the afterModel on PostRoute (postsController.get(‘allCountries’) pattern) worked fine then?

Yes, I’m using that method thank you. I choose your answer on SO.