Nested routes, parent model access and promises as models #1709

Closed
wildchild opened this Issue Jan 7, 2013 · 8 comments

Comments

Projects
None yet
4 participants

Hi there. Previous router and new router implementation does not provide smooth mechanism for accessing parent models. Yes, there is modelFor, but it does not work as expected, when models are promises.

This is also true for a case when you routes are nested and have dynamic segments. In my app children models have composite keys. In other words, children model can not be fetched without parent model instance.

I tried to find a workaround and found some issues instead. I would like to share them:

  1. Ember provides Route#model method to supply a model instance, but params object in Route#model method always returns only last dynamic segment. Shouldn't parent segments be there?
  2. On direct URL access child route's model methods executes before parent. Turns into a problem in attempt to access parent models.
  3. Route#modelFor returns undefined for parent model on step by step transition. Well, this is because #model will not be fired on parent, thus user must manually set this.currentModel in setupController. Hope this will be documented or fixed somehow.
  4. If models are promises, according to @wycats's comment in setupController you shouldn't get a promise. This is not so.

I created a fiddle for demonstration, this link for direct URL access.

Contributor

Nopik commented Jan 14, 2013

It seems, that point 2 of your description is captured in #1736

Owner

wagenet commented Jan 15, 2013

Answers are as follows:

  1. You can use this.modelFor(parentName) to get the parent context.
  2. Should be fixed by d269fa3
  3. Should also be fixed by d269fa3
  4. If you pass a promise in via transitionTo, we don't do anything special, nor do we intend to. If you want special behavior you should resolve the promise before calling transitionTo.

If I'm misunderstanding you on any point, please let me know and we can reopen.

@wagenet wagenet closed this Jan 15, 2013

@wagenet,

  1. You can use this.modelFor(parentName) to get the parent context.

Ok, using it. I was unable to use it before your fix.

  1. Should be fixed by d269fa3

Not fixed. Children still runs before.

  1. Should also be fixed by d269fa3

Fixed. Thank you.

  1. If you pass a promise in via transitionTo, we don't do anything special, nor do we intend to. If you want special behavior you should resolve the promise before calling transitionTo.

I never pass a promise in via transitionTo. Just my Model#find returns promises. Check out fiddle below.
According to @wycats,

If a model hook returns a promise, the router moves into the loading handler, and waits for the promise to resolve

If I understand this right, I should never get promise from #modelFor or #controllerFor(routeName).get('content') if my #model hook returns promise.
Note that this is only applies to direct URL access. In step by step transition it works as expected. Shouldn't direct URL access behave like regular transition?

Check this updated fiddle. Direct URL fiddle for reproducing.

Owner

wagenet commented Jan 15, 2013

Ok, looks like (2) is covered by #1769.
As for (4), you misunderstood @wycats. The Ember Data objects are both models AND promises. So yes you get a promise returned, because all Ember Data objects are also promises.

As for (4), you misunderstood @wycats. The Ember Data objects are both models AND promises. So yes you get a promise returned, because all Ember Data objects are also promises.

@wagenet, probably you misunderstood me instead. I could have been clearer. I get a promise returned, but I don't use Ember Data. In a fiddle above I have an example how my Model#find works.

Looking at docs: packages/ember-routing/lib/vendor/router.js#L325

If the `deserialize` method on a handler returns a promise
(i.e. has a method called `then`), this function will pause
building up the `HandlerInfo` Array until the promise is
resolved. It will use the resolved value as the context of
`HandlerInfo`.

It works, but does not work with nested routes. If my nested model depends on parent, I must use modelFor in child route model hook. It will return a promise of parent model instead of instance. To retrieve children I need an ID of parent. I don't think that router should behave like this. It turns in a hell of promises. If it "resolves" from model hook then it should also wait for promise to be resolved before setting it to currentModel d269fa3. That's what I exactly wanted to say.

@wagenet wagenet reopened this Jan 17, 2013

Owner

wagenet commented Jan 17, 2013

@wildchild Ok, I think I understand what you're saying. I'll look into this more.

Owner

wagenet commented Jan 17, 2013

@wildchild Since the remaining issue is pretty well encapsulated in #1642 I'm going to reopen that one.

@wagenet wagenet closed this Jan 17, 2013

salper commented Feb 4, 2013

Hi !

I'm using a nested route this way :

App.Router.map(function () {
  this.resource('element', { path: ':element/element_id' }, function () {
    this.route('edit');
  });
});

The ElementEditRoute retreives its model from the parent view :

App.ElementEditRoute.extend({
  model: function () {
    return this.modelFor('element');
  }
});

Everything works fine when i navigate using the element route first, then the edit route. But when I enter the application directly in the state element/1/edit, a blank page is displayed, without any thrown error. I don't know if I'm using Ember correctly, but here's what I found.

The problem can be avoided using the Ember Data identity map, but digging further, I found that the blank page was due to nested promises :
rsvp:230 :

function resolve(promise, value) {
  RSVP.async(function() {
    promise.trigger('promise:resolved', { detail: value });
    promise.isResolved = true;
    promise.resolvedValue = value;
  });
}

When trigger is called, the promise is not in a resolved state, as isResolved is affected after the call. Consequently, in the routing process (very simplified) :

  • collectObjects is called, given the application route argument
    • callObjects is called given the element route argument
    • ElementRoute.model() hook is called, returning a promise
    • the promise is resolved, thus triggering the resolve event
      • callObjects is called given the element edit route argument
      • ElementEditRoute.model() hook is called, returning the previous promise (the same model as the parent route)
      • as the promise is not in a resolved state, the proceed callback will be waiting for a resolve event (completely stucked)
    • the promise resolved flag is set to true, no more resolve event will be triggered

In this case, updating the code to :

function resolve(promise, value) {
  RSVP.async(function() {
    promise.isResolved = true;
    promise.resolvedValue = value;
    promise.trigger('promise:resolved', { detail: value });
  });
}

solves the problem. It makes sense in a way to change the state of the promise before reporting its new state to its listeners. But I may be missing something.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment