A coworker and I recently encountered a seemingly innocuous Ember route and a

misbehaving Ember app. Nearly convinced it was an Ember bug we stepped back and

questioned our assumptions. After journeying through Ember’s docs and source

code we discovered that the apparent bug was actually intended behavior. It

turns out that Ember’s Route.setupController hook behaves a little differently

if it’s invoked with an undefined model rather than a null one.

This post walks through the counterintuitive behavior we encountered and the

steps we took to find out what was really going on.

I’ll start by setting up an Ember twiddle that exposes the seemingly buggy behavior.

Recreating the bug with Ember Twiddle

Here’s the ember twiddle

I’ll be using in this post. It’s contrived to illustrate the problem

so admittedly the code will feel a little funky.

The twiddle app will display recipes and ingredients for different types of

foods. You’ll notice a ‘foods’ and ‘recipes’ segment in the url at the top.

Those two url segments correspond to a set of routes:

From the routing, the ‘recipes’ route and UI are both nested inside the ‘foods’:

Clicking on a food type on the left will add a query param and filter the recipes:

You can also go to an ingredients page for each type of food:

The problem

Clicking on a food type to filter, then clicking back to ‘All foods’ will clear

out the query param, but the filter on recipes stays the same.

Where to begin? Start at the template



When presented with behavior I’m not expecting, I always start at the template if the app is rendering

correctly. From there I pretty much follow a flowchart

to avoid jumping to any conclusions. Starting in the templates also means I can have almost zero knowledge

about the app itself and still debug like a champ!

In the template the {{recipeName}} and filteredRecipes values both don’t

match what I would’ve expected them to be. Since this is a controller-backed

template those values both had to be set on the controller at some point.

Both of those are computed properties that depend on 'model' , which isn’t set directly anywhere in this file. I’ll assume

that ‘model’ is set on the controller by its corresponding route, in this case routes/foods/recipes.js

The model hook is short, at least. this.modelFor('foods') (api docs)

is getting the return value of the foods route’s model hook. Since foods is

a parent route, I know that by the time the recipes route runs foods will

already have its model so this looks fine. My first assumption is that the

model hook must not be running when the foodId query param isn’t set (for

whatever reason), but to confirm it I’ll stick a console log into it and click around.

Completely wrong! The model hook runs every time. I must be missing something.

It’s also worth pointing out that the model hook doesn’t actually set the

model on the controller. That’s the job of the (aptly named) setupController

hook, which in this route is just the default implementation. It’s mentioned briefly

at the end of the routing guide,

but it’s not a hook I end up having to use too often. Maybe that’s not

running? I’ll implement the hook myself and go from there.

Gah! My version of the setupController hook works as I’d expect. How does Ember’s implementation

differ? It’s worth pointing out that Ember’s API docs are quite good at this point, and there are

links to the source of each method. Here’s the code on github linked from the

setupController docs.

And there is the assumption that I didn’t know I’d made: the default

implementation of setupController doesn’t set the model if the model hook

returns ‘undefined’, which is not what I’d expected. At this point I have two

ways to fix my problem: I could keep the setupController function I’ve

defined, or I could make the model hook return null instead of undefined .

Stepping back

The process we used to find out what was going on reinforced a good rule of

thumb when it comes to debugging: Start with your code first. I think my code

is doing X.

It’s much easier to deal with code you wrote first, and then move onto the

code you didn’t write, rather than the other way around. This helps put all of

the assumptions you have about your code out on the table. With any framework

there’s a macro set of assumptions that come with it and validating (or

invalidating) assumptions on your code will give you a more direct route.

In this particular example we thought one of Ember’s route hooks was going to

behave a certain way. It did except for the case where it was dealing with an

undefined value. Who would have thought undefined would behave

differently?

When understanding the expected behavior the Ember guides and the API docs are

the best places to start. When what you’re looking for isn’t mentioned the next

place to go is the test suite.

For example, Ember Data has a great test suite that has helped clarify how to

use things like

polymorphism

that weren’t in the guides until

recently.

Lastly, the Ember source code is great! Things are clearly packaged up, and in a

pinch the whole thing is right there in your dev tools. As you run into

problems, make sure you’re not incorrectly assuming what should be happening

before going down the rabbit hole.