Nested resources and dynamic segments


#1

I have been working with Ember for awhile and the one point I can’t seem to really fully understand is how nested resources work. I have the following router (similar to that from the docs):

App.Router.map(function() {     this.resource('post', { path: '/post/:post_id' }, function () {         this.resource('comments', function () {             this.route('new');         });     }); });

Based on the docs, I would expect that I could use the dynamic segment post_id in my route model hook for the CommentsRoute, CommentsIndexRoute and the CommentsNewRoute, but that dynamic segment doesn’t make it into my model hook for those routes, only for the PostRoute and PostIndexRoute routes. I’m wondering what I’m doing wrong or if I have a misunderstanding of the Resource/Route relationship. My routes look like this:


App.PostRoute = Ember.Route.extend({
    model: function (param) {
        return App.Post.find(param.post_id);
    }
});

App.PostIndexRoute = Ember.Route.extend({
    model: function (param) {
        return App.Post.find(param.post_id);
    }

})

App.CommentsRoute = Ember.Route.extend({
    model: function (param) {
        return App.Comment.find(param.post_id);// the post_id is not available here
    }
});

App.CommentsIndexRoute = Ember.Route.extend({
    model: function (param) {
        return App.Comment.find(param.post_id);// the post_id is not available here
    }
});

My ultimate goal is to have my comments model hook use the post_id to retrieve the comments for that post. Please help me to understand why I’m not receiving the post_id in my Comments and CommentsIndex model hooks. Thank you for your help.


Accessing dynamic segments in a nested route
#2

Dynamic segment values are only available to the route that the dynamic segment belongs to, not the child routes. I don’t know the full reason why this is so, but I can say that this is how it should be. In order to avoid duplicating code, only one route should have to resolve the dynamic segment. All child routes should just use this.modelFor('post') to get the model directly and not have to resolve the segment again. So you’re routes should look something like this:

App.PostRoute = Ember.Route.extend({
    model: function (param) {
        return App.Post.find(param.post_id);
    }
});

App.PostIndexRoute = Ember.Route.extend({
    model: function () {
        return this.modelFor('post');
    }
})

App.CommentsRoute = Ember.Route.extend({
    model: function () {
        return App.Comment.find(this.modelFor('post').get('id'));
    }
});

App.CommentsIndexRoute = Ember.Route.extend({
    model: function () {
        return App.Comment.find(this.modelFor('post').get('id'));
    }
});

Also, I have a few unrelated comments on your code that you might find useful.

  • You’re using a pretty old version of Ember-Data (judging by the find() calls). You’ll probably want to upgrade to the newer version if possible.

  • I don’t know how your data model is setup, but it seems like you’d want to make posts and comments related with relationships. Something like:

App.Post = DS.Model.extend({
    comments: DS.hasMany('comment', { async: true })
});

App.Comment = DS.Model.extend({
    post: DS.belongsTo('post', { async: true })
});

Then for your model hook, you can just do something like this:

App.CommentsIndexRoute = Ember.Route.extend({
    model: function (param) {
        return this.modelFor('post').get('comments');
    }
});
  • You declared the model hook for the posts resource and the comments resource. But, you only have to declare the model for a route if you’re using the model in the template. For example, if you don’t have a comments template declared, or you’re just using it as an outlet, you don’t have to declare App.CommentsRoute. Ember will generate one for you. I don’t know your exact layout, but it’s possible that you have extra routes declared. If you wanted to post some more of your code, I might be able to help you eliminate some duplicated code.

#3

gordon_kristan thank you so much, this explained some things I was not grasping. I haven’t integrated Ember data yet because this is a project that I am picking up after a little gap and ember data was not production ready when we started. I also took your not on the extra routes. I thought I understood the point of index routes but I am not seeing the need as much now because I am finding myself putting much of the template content in the main resource template, am I missing something? Again, thank you so much for the explanation.


#4

I would really recommend turning on the debugging mode that logs all router transitions, and also logs every single object created by the resolver “under the hood” that Robert Jackson recently put into Ember… also if you haven’t looked into it, the ember chrome inspector plugin is really worthwhile using.

[ edit ] this page explains a lot of the details I’m talking about: http://emberjs.com/guides/understanding-ember/debugging/

This is the PR that contains the LOG_RESOLVER issue… but I noticed it’s not mentioned in the documentation for debugging. I’ll see if I can get it in.

[ further edit ] added a PR mentioning it now: https://github.com/emberjs/website/pull/1493


#5

This is now automatic in newer versions of ember. So if you have the following routes:

App.Router.map(function() {
  this.resource('post', function() {
    this.route('editPost');
  });
});

Then the model for editPost route will automatically be as if it was this.modelFor('post') unless you override it. This only works for routes that are children of a resource, not resources.


#6

I’m late to the game, but I’ve used the following method to get all the active parameters:

  getActiveRouteParameters: function () {
        var parameters = {};
        try {
            App.Router.router.currentHandlerInfos.forEach(function (handler) {
                Ember.merge(parameters, handler.params);
            });
            return parameters;
        } catch (error) {
            return {};
        }
    }