Why do nested resource routes reset the namespace in Ember? - ember.js

Let's say I have a Photo model and a Post model, and I want both of those to have Comment's. In Rails, my routes would look like this:
Rails.application.routes.draw do
resources :posts, only: [ :show ] do
resources :comments, only: [ :index ]
end
resources :photos, only: [ :show ] do
resources :comments, only: [ :index ]
end
end
This generates the following routes:
GET /posts/:post_id/comments(.:format)
GET /posts/:id(.:format)
GET /photos/:photo_id/comments(.:format)
GET /photos/:id(.:format)
Okay, makes sense. If I want to get the path to the Comment's for the Photo with an ID of 9, I'd use photo_comments(9).
If I wanted to create the same routes in Ember, I'd do:
App.Router.map () ->
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }, ->
#resource 'comments'
#resource 'photos', ->
#resource 'photo', { path: '/:photo_id' }, ->
#resource 'comments'
In Ember, this generates the following URLs:
#/loading
#/posts/loading
#/posts/:post_id/loading
#/posts/:post_id
#/posts
#/photos/:photo_id/comments
#/photos/:photo_id/loading
#/photos/:photo_id
#/photos/loading
#/photos
#/
#/photos/:photo_id/loading
I still have /posts/:posts_id/comments and /photos/:photo_id/comments, which is what I wanted. However, because Ember resets the namespace, I no longer have post_comments and photo_comments helpers. I have a comments route, which routes to /photos/:photo_id/comments, but I don't have any way of routing to /posts/:posts_id/comments. I realize I could fix this by doing the following, but it seems redundant:
App.Router.map () ->
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }, ->
#resource 'posts.comments', { path: '/comments' }
#resource 'photos', ->
#resource 'photo', { path: '/:photo_id' }, ->
#resource 'photos.comments', { path: '/comments' }
TL/DR:
I understand that Ember resets routes for nested resources, but I don't understand why. Could somebody explain it to me?

TL;DR Resources must be unique due to the transitioning paradigm, and really you're just over-writing the comments resource.
It's because when you transition to routes you don't explicitly call out an entire path.
this.transitionTo('photo', photo);
{{#link-to 'photo' photo}} My photo{{/link-to}}
Because of this resources must be unique.
If you were at the root of your app and wanted to jump 2 levels deep, to a photo you would just use this.transitionTo('photo', photo), and would NOT use this.transitionTo('photos.photo', photo)
If you are transitioning to a resource that is multiple dynamic resources deep you just send in multiple models. this.transitionTo('foo', bar, baz).
As was implied, you could force people to state an entire path while doing transitionTo/link-to, but the authors decided to punish the smaller percent of people with duplicate resources vs punish everyone into defining the entire path while transitioning.
Additionally it's understood that foo.bar represents resource.route in Ember. I wouldn't consider this an argument for why it was architected as it was, more of a statement about it.
#resource 'posts', ->
#resource 'post', { path: '/:post_id' }
#route 'foo'
this.transitionTo('posts.foo');

Related

Creating a new resource in ember

I'm building an office reception app in Ember. When a person arrives at the office, they pop open the app and are taken through a three step wizard:
Choose a reason for visiting
Choose the person you've come to see
Confirm
The app also allows an administrator to view all of the visits and to view an individual visit.
I have Visit and Person models I've hooked up to a server using Ember Data. Here's what my routes look like:
App.Router.map () ->
#resource 'visits', ->
#resource 'visit', { path: '/:visit_id' }
#resource 'new', ->
#route 'welcome'
#route 'directory'
#route 'thanks'
This allows me to create a new Visit model in the VisitsNewRoute and use it in the welcome, directory and thanks views.
This works. However, it feels wrong to have a new resource, especially since it's conceivable I'll want at least one more new route in my application.
Is there a better way to do this?
I think that you can change the new resource to newVisit like this:
App.Router.map () ->
#resource 'visits', ->
#resource 'visit', { path: '/:visit_id' }
#resource 'newVisit', ->
#route 'welcome'
#route 'directory'
#route 'thanks'
Now you will have a NewVisitRoute where you can create a new Visit model to use in each of the child routes.
And you will be able to make a transition to this routes with the route names: newVisit.welcome, newVisit.directory and newVisit.thanks. You can use this route names in a link-to helper link this:
{{link-to "Welcome", "newVisit.welcome"}}
The recommended practice is to use a create/new route under the resource type, so new under visits, then `transitionTo('visit.welcome', newRecord). (I'm saying all of this with the assumption that welcome, directory, and thanks aren't part of the new record creation).
App.Router.map () ->
#resource 'visits', ->
#route 'new'
#resource 'visit', { path: '/:visit_id' }
#route 'welcome'
#route 'directory'
#route 'thanks'
Ember doesn't always name routes the way you want when dealing with routes nested more than one level. I would name your 'new' route as follows:
#resource 'visits.new', path: 'new', ->
There are a number of approaches you can use to structuring your routes depending on how you assign model ids and whether or not you are using localStorage to preserve user edits until they are persisted to the server.
I have my route pattern as follows:
App.Router.map () ->
#resource 'visits', ->
#route 'new'
#route 'crud', path: ':visit_id'
My 'new' routes create a new resource in the routes model callback which in my models auto-generates a v4 UUID. The new route then performs a transitionTo the crud route in the afterModel callback. In effect the 'visits.new' route acts as a trampoline and allows you to easily use {{link-to 'visits.new'}} from templates/menus etc.
The above approach allows you to to have a single crud route and crud controller that can handle all the show/create/update/delete actions for the model. The models isNew property can be used within your templates to handle any differences between create and update.
I also use localStorage so that newly created (but not yet persisted) models survive a browser refresh, the UUIDs really come in handy for both this and for persisting complex model graphs.
The above router pattern occurs quite a lot in my app so I have defined some base Route classes and a route class builder but the general pattern is as follows:
If using UUIDs:
App.VisitsNewRoute = Em.Route.extend
model: (params, transition)->
App.Visit.create(params)
afterModel: (model,transition) ->
#transitionTo 'visits.crud', model
App.VisitsCrudRoute = Em.Route.extend
model: (params,transition) ->
App.Visit.find(params['visit_id'])
If not using UUID's then the routes are different. I did something like this before I moved to UUIDs, it treats model id 'new' as a special case:
App.Router.map () ->
#resource 'visits', ->
#route 'crud', path: ':visit_id'
App.VisitsCrudRoute = App.Route.extend
model: (params, transition) ->
visit_id = params['visit_id']
if visit_id == 'new' then App.Visit.create() else App.Visit.find(visit_id)
serialize: (model, params) ->
return if params.length < 1 or !model
segment = {}
segment[params[0]] = if model.isNew() then 'new' else model.get('id')
segment
For your specific case of managing the wizard step state I would consider using Ember Query Params, which allow you specify the current step in a parameter at the controller level
Query params example:
App.VisitsCrudController = Em.ObjectController.extend
queryParams: ['step'],
step: 'welcome'
Link to next step in the view:
{{#link-to 'visits.crud' (query-params step="directory")}}Next{{/link-to}}
You may also want to define some computed properties for the next and previous steps, and some boolean properties such as isWelcome, isDirectory for your view logic.

Share a resource between two paths

I am fairly new to JS and Ember, so please bear with me =)
In my app there are two types of users: a superuser, and program managers. The superuser makes new programs and assigns a manager to them. I then need the program managers to be able to go to a page where they can choose what program they want to work on and have things paths from there.
This is how my current NON-WORKING router.coffee file looks:
App.Router.map ->
#resource 'su', ->
#resource 'programs', ->
#route 'new'
#route 'edit', path: ':program_id/edit'
#resource 'pm', ->
#resource 'programs', path: 'pm/programs', ->
#resource 'projects', path: ':program_id/projects', ->
#route 'new'
#route 'edit', path: ':project_id/edit'
I think having programs listed as a resource twice is causing my app to get a "uncaught typeerror cannot call method "shouldSupercede" of undefined" because it works when i take out programs from pm.
I am unsure of how to add a page that lists all the programs a pm is assigned to
first of all you should think of a resource as something that matches with a data model, and a route as an action that can be performed on that data model, or with it as basis. It is common error in Ember beginners to try to somehow put the model relations in the way routes are organized. The typical example of 'post' and 'comment' resources has the trap that comments belong to 1 post. And even there the resource nesting is probably unneeded, as the complexity should rely in the model relations, not in the routing.
So in general, the architecture you propose is going to scale badly. Specially if at some point you decide that e.g. 2 project managers participate in the same program. Hence, there is no need to nest routes inside the others in this case (as it occurs for example, when putting the 'pm' resource and routes inside the 'su' resource).
I would keep all entities separated at route level, and then do the work establishing the relations between the models. You can work this by hand if you like, or you could let e.g. Ember Data do that for you (first please check if it suits your needs, as it might be oversized). Your backend should know which projects/programs, a user should be able to see.
So here's my suggestion to organize routes: (I even probably do just a simple 'user' resource and let the superuser or project manager status be a property in the model, but I don't know your data models, so let's assume they have different data structure...)
App.Router.map ->
#resource 'su'
#resource 'project_managers' path: '/project_managers' ->
#route 'index'
#resource 'project_manager', path: '/:project_manager_id', ->
#route 'show, path: '/' #at this route you could show info about the project manager, and its programs could be accessible through its model, like, e.g. model.programs.
#route 'new', path: '/new'
#route 'edit', path: '/edit'
#route 'programs', path: '/programs' #or you could set a new route and a view for seeing the programs for this pm. (note *1)
#resource 'programs', path: '/programs' ->
#route 'index' #this route would provide a list of all programs (naturally you can decide which one are visible to the current user)
#resource 'program', path: '/:program_id', ->
#route 'show, path: '/'
#route 'new', path: '/new'
#route 'edit', path: '/edit'
#resource 'projects', path: '/projects', ->
#route 'index'
#resource 'project', path: '/:project_id', ->
#route 'show, path: '/'
#route 'new', path: '/new'
#route 'edit', path: '/edit'
*1: Because even in case that from there you'd like to navigate further to the specific program, there is no gain in going to a path such as '/project_managers/35/programs/4' rather than '/programs/4'. Even if the URL for an API request could be the first one (quite probable if you operate a Rails backend).
Sorry if the answer was too long, I hope it helped you to have a clearer view! :)

Create a nested route with a dynamic segment in Ember.js

I have a nested edit route for one of my resources:
#resource 'organization', path: 'organizations/:organization_id', ->
#resource 'organization.edit', path: '/edit'
I link to it like this (using Emblem.js):
linkTo 'organization.edit' organization | Edit
Unfortunately, this results in a url like:
/organizations/4#
Rather than the expected:
/organizations/4/edit
Any idea why this is happening? I experimented with the route syntax a lot. Removing path for organization.edit does nothing, as does a full path: 'organization/:organization_id/edit.
You should be able to get your desired result by using this type of nesting structure:
App.Router.map(function() {
this.resource("organizations", function(){
this.resource("organization", { path: "/:organization_id" }, function(){
this.route("edit");
});
});
});
JSBin example
You're on the right track but #resource is really intended for objects, e.g. organizations. If you're defining an action (not nested resources) you'll want to use #route, i.e.:
#resource 'organization', path: 'organizations/:organization_id', ->
#route 'edit'
I believe that should give you the expected behaviour / routing.
Why not use something like this:
#resource 'organization', ->
#route "edit",
path: "/:organization_id/edit"

Routing Links to parent routes in Ember JS

In my application I have the following route setup
Orders.Router.map ->
#resource "orders", path: '/', ->
#route 'new'
#route 'show', path: ':order_id'
#resource 'items', path: ':order_id/items', ->
#route 'new'
class Orders.ItemsNewRoute extends Ember.Route
model: (params) ->
Orders.Order.find params.order_id
Within my items.new route, I would like to have a link back to orders.show and am unable to find the best way of going about this.
I cannot find a way to bind my parameter from my URL to the linkTo helper. What would be the best way to go about this?
Ryan,
I'm not 100% sure I understand what your question is but linkTo is very simple to use.
{{#linkTo "orders.show" controller.content}}Show My Order{{/linkTo}}
The above code will send you to the orders.show route with the model being the content of your controller.
Steve
I don't think this is the best way to go about this, but I was able to mess about with the router a bit to make it more like this:
Orders.Router.map ->
#resource "orders", path: '/', ->
#route 'new'
#route 'show', path: ':order_id'
#resource 'items', path: ':order_id/items/new'
class Orders.OrdersRoute extends Ember.Route
model: ->
return Orders.Order.find()
class Orders.OrdersIndexRoute extends Ember.Route
model: ->
return Orders.Order.find()
class Orders.ItemsRoute extends Ember.Route
model: (params)->
return Orders.Item.createRecord
order: Orders.Order.find params.order_id
Then in my template I reference {{#linkTo 'orders.show' order}}
It seems that it was not able to grab a reference from the original :order_id wildcard.

nested routes mapping

This is the setup that I have
DS.RESTAdapter.configure 'plurals',
child: 'children'
App.Router.map ->
#resource 'people', ->
#resource 'person', path: ':person_id', ->
#route 'children'
App.PerosnChidlrenRoute = Ember.Route.extend
model: -> App.PerosnChidlren.find()
So, there is two problems here: first is that PersonChildren hitting /person_childrens instead of /people/:person_id/children.
And the second is incorrect pluralization as you can mention:)
Can you please explain how do I setup routing and pluralization correctly?
p.s. I've tried to put everything up in http://jsfiddle.net/UJkn3/1/