ted.strauss
ted.strauss

Reputation: 4339

Ember routes - dynamic segments - wiring models together

I would like to use ember routes and models to build some "ambitious" web apps. Here's what I'm trying to do. I have a book and author model. While showing a list of author resources, if the user clicks on an author, display via a nested route all the books by that author. So I'd like to treat the author id (or name) as the dynamic segment to pass to the book model. But there seems to be a limitation whereby dynamic segments must be the primaryKey of the current model, no other model attributes or models can be used.

Do I need to write a custom de/serializer? If so, an example fiddle applicable to this scenario would be much appreciated.

Here is some example code describing what i'm trying to do, with specific questions appearing in comments.

Authors.hbs template

        <div class="span3">
            <h4>Books</h4>
            <ul class="unstyled">
                {{#each model}}
                    {{#linkTo 'authors.books' this}}<li>
                        {{name}} <small>{{date dob}}</small> <small> {{origin}}</small>
                    </li>{{/linkTo}}
                {{/each}}
            </ul>
        </div>
        <div class="span8">
            {{outlet}}
        </div>

initialize.coffee

@resource 'books', ->
  @resource 'book', {path: ':cube_id'}
@resource 'authors', ->
  @resource 'AUTHOR', {path: ':AUTHOR_id'}
  # QUESTION How to make the next line retrieve books-by-author from the book model?
  @route 'books', {path: '/books/:author_id'}

AuthorRoute

module.exports = App.AuthorsRoute = Em.Route.extend
    model: ->
        App.Author.find()

BookRoute

module.exports = App.BooksRoute = Em.Route.extend
    model: ->
        App.Book.find()
    # QUESTION How should the model be extended to allow for lookup by attribute?
    # model: (params) ->
    # App.Book.find { author: params.book_id }

Author model

module.exports = App.Author = DS.Model.extend
    name: DS.attr 'string'
    origin: DS.attr 'string'
    dob: DS.attr 'date'

Book model

module.exports = App.Book = DS.Model.extend
    name: DS.attr 'string'
    author: DS.attr # QUESTION How to wire together models here?
    #author: DS.belongsTo 'App.Author'
    publisher: DS.attr 'string'
    published: DS.attr 'date'

Upvotes: 0

Views: 131

Answers (1)

MilkyWayJoe
MilkyWayJoe

Reputation: 9092

Sometime ago I've answered a somewhat similar question (and it's also described in the guides) how to link models with associations such as belongsTo (when a model record belongs to another model) or hasMany (when a model record has many records of another model associated to it) and how to use multiple models in the same route.

With associations provided by Ember-Data, you can create properties in your model that are (a) a collection of associated records or (b) a single record that is parent of the record in question. And then in your view layer, simply refer to these properties (collection or otherwise) by the name (see proposed template below).

As for your particular app, you can write your models as proposed below:

module.exports = App.Author = DS.Model.extend
    name: DS.attr 'string'
    origin: DS.attr 'string'
    dob: DS.attr 'date'
    books: DS.hasMany 'App.Book'

module.exports = App.Book = DS.Model.extend
    name: DS.attr 'string'
    author: DS.belongsTo 'App.Author'
    publisher: DS.attr 'string'
    published: DS.attr 'date'

Note that now both models know about each other. This model declaration will give you a data structure similar to the representation below. So every time you have an instance of Book, you can use its property author to access properties defined in the Author model, such as dob, name.. etc

     _____________________              ______________________
    |       Author        |            |          Book        |        
    |---------------------|            |----------------------|
(PK)| + id: int           |◄-\    |--►►| + id: int            |(PK)
    | + name: string      |   \   |    | + name: string       |
    | + origin: string    |    \--Ԑ----| + author: App.Author |(FK)
    | + dob: date         |      /     | + publisher: string  |
(FK)| + books: App.Book[] |-----/      | + published: date    |
    |_____________________|            |______________________|

Relying on the associations you'll be able to refer to Author.books in your view layer (when iterating a collection that's a property of your controller or model, use content.books in your view) to get a collection of books by that author and use it with an {{#each}} block. As an example, take a look in this fiddle, particularly in the template tags/tag in which I am iterating through a list of posts associated with a given tag. Although the sample is for "blog", the same concept can be used for whatever entity type you're currently working with.

So in your case, even in the "authors.author" route, the template could look like the following:

<h3>Books written by <em>{{name}}</em></h3>
<ul>
    {{#each content.books}}
        <li>
           {{#linkTo books.book this}}
               <i class="icon-book"></i> 
               <!-- consider a helper to display the year or format the date
                    instead of "publisehd" which will display an ugly date -->
               {{this.name}} (this.published) 
           {{/linkTo}}
        </li> 
    {{else}}
    <li>
        <div class="alert">
            <a class="close" data-dismiss="alert" href="#">&times;</a> 
            This author hasn't written any books that we know of.. seriously...
        </div>
    </li>
    {{/each}}
</ul>

Note that the {{#each}} helper is iterating through the collection, that is, instances of Book model which are available through the associations in the model declarations.

Upvotes: 0

Related Questions