'params' are not usable in a nested route's model() hook?


#1

(FYI, my Ember version: 1.0.0-rc.3)

Looking at the documentation for specifying a route’s model (http://emberjs.com/guides/routing/specifying-a-routes-model/), I’m expecting for my nested route’s model() hook to get passed params with a dynamic segment.

However, what I’m discovering is that I get passed some empty (?) object instead - and in order to set the nested route controller’s model properly, I have to take reference from the container route controller…

App.Router.map( function() {
    this.resource( 'orders', { path: '/' }, function() {
        this.resource( 'order', { path: 'order/:order_id' }, function() {
            this.route( 'overview' );
        });
    });
});

App.OrderOverviewRoute = Ember.Route.extend({
    model: function( params ) {
        // params is NOT what u'd expect here!
        return this.controllerFor( 'order' ).get( 'content' );
    }
});

Is this normal / expected? I understand how it might make sense (you don’t need to find the same model more than once!), but it can certainly throw somebody off…


#2

At Discourse we seem to avoid the model hook, cause linkTo does not trigger it.


#3

What is it you are trying to do? Ember should have already obtained the order model in App.OrderRoute. Having multiple copies of the order model seems like a recipe for disaster.

App.OrderRoute = Ember.Route.extend({
    model: function( params ) {
        return App.Order.find(params); // or whatever you are doing to resolve this
    }
});

App.OrderOverviewRoute has no params listed for ember to parse and send while fetching the model. It only parses the portion of the path related to that particular resource. I’ve laid out an example below.

this.resource( 'dog', { path: '/dog/:dog_id/:bark_id' }, function() {
    this.resource( 'order', { path: 'order/:order_id' }, function() {
         this.route( 'overview' );
    });
 });

consider a url like this: #/dog/3/2/order/2/overview

It would hit the following routes like this

// This guy hits first with params having dog_id=3 and bark_id=2

App.DogRoute = Ember.Route.extend({
    model: function( params ) {
       return{};
     }
 });

// This guy hits second with params having order_id=2

App.OrderRoute = Ember.Route.extend({
    model: function( params ) {
       return{};
     }
 });

// This guy hits third with params that have nothing in it

 App.OrderOverviewRoute = Ember.Route.extend({
     model: function( params ) {
        return{};
     }
 });

Hope this helps


#4

Just out of curiosity, what are you using instead of the model hook? Are you manually building your models etc?


#5

Stuff like this unfortunately, model just does not seem to work for us most of the time cc @wycats

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/admin/routes/admin_groups_routes.js


#6

Gold mine, thanks Sam.


#7

yeah, I am aware. But u’ve still got to use model() somehow?

if you don’t enter the route via a linkTo, but say via the URL, you will need to call model() in order to construct the model… This selective calling is useful for the cases when the model context is not provided?


#8

What is it you are trying to do?

I am trying to get access to the model in order to display specific info (kind of like “master-detail-detail”, if you’re familiar with that terminology - with “overview” being the the last “detail”) about the order. There’s tons of stuff we’d like to display / functionality we’d like to enable with the order, and that’s just how we’ve designed our interface (there are other routes besides “overview”, just not listed in my code)

Having multiple copies of the order model seems like a recipe for disaster.

Yeah. So I figured. I understood that; but since this wasnt covered in the documentation, I was wondering whether this was by design or not… (Technically, though, if we had an identity map, it would still be the same order instance? But of course, this could very well be a trap to watch out for if you’re implementing your own data layer!)

App.OrderOverviewRoute has no params listed for ember to parse and send while fetching the model. It only parses the portion of the path related to that particular resource. I’ve laid out an example below.

Thanks!!! Veery helpful!!! So I guess that answers my question in that yes, this is by design, and normal and expected… I just didnt know what to think, seeing as how this is not talked about at all in the guides.

EDIT: ok wait, my bad: it just occurred to me that this is talked about in the guides! Just not in the place which I first thought of to go to to check out when I first encountered this problem: http://emberjs.com/guides/routing/specifying-a-routes-model/. But it is right there, in http://emberjs.com/guides/controllers/dependencies-between-controllers/… So I guess doing it this way (as per this last url) is the recommended way?


#9

Awesome, yes, that’s the route… pattern you want to us.

And to what Sam was saying, you don’t necessarily have to use the model hook. You can just set the model in setupController (which is execute every time the context changes) or in activate (which is executed only the first time the route is entered Link to activate documentation).

As of Ember 1.0 RC1, there are public hooks: activate and deactivate. Note that the activate hook will run only when a route handler is activated for the first time. If a route handler’s context changes, the setupController hook will run again, but not the activate hook. link to RC1 blog info

Random note, if you don’t want to use the model hook, just remove it from the route like Sam’s code :smile:


#10

got it. Thanks for the elaboration!

As for “just remove it from the route”… I guess that would only work if there is no default hook? http://emberjs.com/guides/routing/specifying-a-routes-model/ mentions a default hook. Are you saying that that only applies for routes with dynamic segments?


#11

I actually really like the model hook and want to start using it more. We have a lot of code left over from before the latest router that didn’t port to the model hook perfectly but I imagine we’ll start using it more often.

linkTo does not trigger it intentionally - if you have the model in memory it will just pass it to the controller rather than fetch it again. Most of the time this is what you want. But because we don’t have an identity map it can be an issue because we’ll sometimes (wastefully) spawn more than one copy of the same model in memory.

We also make a mistake in that sometimes we use the same object for a lightweight presentation (say a topic list) and a detailed presentation (being inside a topic).

What I’d like to do is have the topic model only include the basics, then contain a details object or something (as a has-one relationship) with all the extra info. Then the lightweight and detailed objects are the same everywhere. It works better with an identity map and uses much less memory.

But I digress!

The model hook works well, except when you’ve built an app that is not using it properly always (like Discourse).


#12

Actually, you can access the parent resource’s model from the child route’s model hook by using this.modelFor('…'). You can’t use this.controllerFor('…') yet, because the controller for the parent resource hasn’t been set at the time the child’s model hook is called. The params won’t be passed to child routes as this is likely unnecessary, since what you probably want is the model, which has already been set by the parent’s model hook.

In other words, routes’ hooks are called horizontally, not vertically. So the [abstracted] call-stack for 3 nested routes like these:

App.Router.map(function() {
  this.resource("posts", { path: "posts" }, function() {
    this.resource("post", { path: ":post_id" }, function() {
      this.route("comments");
    });
  });
});

Looks something like this:

postsRoute.model() ➜ postRoute.model(params) ➜ postCommentsRoute.model()
⟀
postsRoute.setupController() ➜ postRoute.setupController() ➜ postCommentsRoute.setupController()
⟀
postsRoute.renderTemplate() ➜ postRoute.renderTemplate() ➜ postCommentsRoute.renderTemplate()
⟀
…

So you can do something like:

App.PostCommentsRoute = Em.Route.extend({
  model: function() {
    var post = this.modelFor('post');
    /* … */
  }
});

#13

It is serialize that gets triggered by linkTo. Serialize converts a routes model into the url.

For dynamic segments, the model hook is only executed when entered via the url. If the route is entered via a transition then a model context is already provided and the model hook is not called. Routes without dynamic segments will always call the model hook.


#14

I dont seem to have a problem here. That’s what I’ve been using so far, and i have no problems.

sorry - horizontal vs vertical… This is the first time that I’ve actually heard of such a terminology. I guess you would be referring to the synchronicity? So events at the same layer are called asynchronously?