0
votes

I have a very simple toy app using Rails as an API and Ember 1.8.1 with CLI for the client.

I have a typical sidebar setup where when you click the item, it displays next to the list. Everything works fine until you refresh the page with a list item on the page (a url that hits the package route), I get the following error:

Uncaught Error: Assertion Failed: The value that #each loops over must be an Array. You passed <sm-client@model:package::ember387:63>

packages/index.hbs

<div class='sidebar'>  <ul>
    {{#each package in model}}
    <li>
      {{#link-to 'package' package}}
      {{package.name}}
      {{/link-to}}
    </li>
    {{else}}
    <li>No contacts found.</li>
    {{/each}}
  </ul>
</div>

application.hbs

<h2 id="title">App</h2>

{{link-to 'Packages' 'packages'}}
{{link-to 'Home' 'index'}}
{{outlet 'sidebar'}}
{{outlet}}

package.hbs

<dl>
  <dt><strong>Name</strong></dt>
  <dd>{{model.name}}</dd>

  <dt><strong>Description</strong></dt>
  <dd>{{model.description}}</dd>
  <ul><strong>Tournaments</strong></ul>
   {{#each tournament in model.tournaments}}
   <li>
    {{tournament.name}}     
   </li>
   {{/each}}
</dl>

router.js

import Ember from "ember";
import config from "./config/environment";

var Router = Ember.Router.extend({
  location: config.locationType
});

Router.map(function() {
  this.resource("packages", function() {
    this.resource("package", {path: "/:package_id"}); });
});

export default Router;

routes/package.js

import Ember from 'ember';

export default Ember.Route.extend({
    model: function(params) {
    return this.store.find('package', params.package_id);
  },

  renderTemplate: function() {
    this.render(); 
    this.render('packages/index', {
      into: 'application',
      outlet: 'sidebar'
    });
 }
});

routes/packages/index.js

import Ember from 'ember';

export default Ember.Route.extend({
    model: function(){
        return this.store.find('package');
    }

});

models/package.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  description: DS.attr('string'),
  tournaments: DS.hasMany('tournament',{
    async: true,
    inverse: 'tournamentPackage'
  })
});

models/tournament.js import DS from 'ember-data';

export default DS.Model.extend({
    name: DS.attr('string'),
    buyIn: DS.attr('string'),
    attempts: DS.attr('string'),
    description: DS.attr('string'),
    tournamentPackage: DS.belongsTo('package', {
    async: true
    })
});

If I comment out {{outlet 'sidebar'}} and visit a package route directly and refresh, it works fine. I'm sure that on reload it's trying to loop through my singular model from the package route in the sidebar. Other threads have mentioned not to return multiple models on dynamic segments so I'm not sure what to do.

Side note: I'm trying to avoid using controllers as I've heard that Ember will move that logic to other pieces of the framework, but if the solution is to use controllers I have no issue using one.

2
Can you add your models definitions? Am I correct thinking that tournaments is hasMany relationship in Package?Kuba Niechciał
Sure, added. For what it's worth this was issue was happening when I was only listing the packages, before I started listing the tournaments in them as well, which is why I didn't think it would be a model issue.NoobException
But where are you listing packages? Looks like that you render index template, but can you add it?Kuba Niechciał
Added it. I feel like using model in this file is part of the problem as it's using a single package as model when I refresh on the route. I tried {{#each package in packages}} as well but I don't think I set up my route correctly for that (I tried returning multiple models with RSVP hash).NoobException

2 Answers

1
votes

Your problem lays in routes/package.js file. Examine what you are doing in the model hook:

model: function(params) {
  return this.store.find('package', params.package_id);
}

You are fetching one package from the API. You have not defined setupController, therefore the result from the model hook is automatically set on model property of your controller (PackageController as default). After that, you make an explicit call to renderTemplate and render current template (package.hbs) AND additional package/index.hbs. This additional template render as follows:

{{#each package in model}}
(...)
{{/each}}

So, what is your model? Your model have not changed. It is still this.store.find('package', params.package_id), so a single package. Rendering multiple templates in one route still uses one, current, controller.

So, how can you obtain what you want? Using {{outlet}} in your packages/index.hbs. You will need to change your templates, but while you enter package route, both index and package controller are active (and both templates are active). Render list of packages as your sidebar in your packages/index.hbs file, at the end add {{outlet}} and see what you got.

Please refer to Routing in Ember guides for more info.

0
votes

I got some help from a friend on this one. You're definitely correct that my routes were messed up. Also, we figured out using a named {{outlet}} isn't the right approach at all on this one. What I ended up doing:

templates/application.hbs

<h2 id="title">Stake Manager</h2>

{{name}}
{{render "sidebar" packages}}

{{outlet}}

templates/sidebar.hbs

<ul>
  {{#each package in model}}
  <li>
    {{#link-to 'package' package}}
    {{package.name}}
    {{/link-to}}
  </li>
  {{else}}
  <li>No contacts found.</li>
  {{/each}}
</ul>

I also made the packages resource go to app root since I'll want some version of that info in a sidebar on each page (for now at least)

Router.map(function() {
  this.resource("packages", { path: '/'}, function(){
    this.resource("package", { path: '/:package_id'} );
  });
});

Tell the application controller what model 'packages' in application route, so we can pass it to the {{render 'sidebar' packages}} in application template. (I will turn this into a component after I have my head 100% wrapped around this)

setupController: function(controller){
        controller.set('packages', this.store.find('package'));
    }
}); 

I think my lack of controller knowledge really through me off here. I think I would've been better off not knowing that some controllers are going away :)