4

I've run into an annoying issue when loading data asynchronously in an ember route's model callback. The issue seems to be that if the model method of my route returns a promise which is rejected then the route will never attempt to re-evaluate that route model. It just automatically returns the same rejected promise the next time it tries to go to that route without even trying to re-fetch the data!

I understand from this answer that an ember route will only call it's model method when trying to convert the url into a model. I'm guessing that in the case of routes with dynamic segments it may be called if it has never encountered that particular dynamic segment before.

Here is what I've got in my router setup.

window.App = Ember.Application.create({
    LOG_TRANSITIONS: true,
    LOG_TRANSITIONS_INTERNAL: true
});

App.Router.map(function() {
    this.route('login');

    this.resource('users', { path: '/users' }, function() {
        this.resource('user', { path: '/:user_id' });
        this.route('create', { path: '/create' });
    });
});

And this is my route.

App.UserRoute = Ember.Route.extend({
    model: function(params) {
        // This returns a promise
        return App.User.fetch(params.user_id);
    }
});

I have some special handling for errors in my application route so that routes which fail due to authentication exceptions redirect the user to the login screen.

App.ApplicationRoute = Ember.Route.extend({
    actions: {
        sessionExpired: function() {
            this.controllerFor('login').set("tokenExpired", true);
            this.transitionTo('login');
        },
        error: function(err) {
            if (err.type === "TokenException") {
                this.send('sessionExpired');
            }
        }
    }
});

The Problem

  1. I navigate to the /users route
  2. For some reason my token expires (inactivity, whatever...)
  3. I navigate to the /users/1 route
  4. The route's model method returns a promise which rejects and I am kicked out to the login screen
  5. I log back in and try to navigate back to the /users/1 route
  6. The route automatically just returns the same failed promise it did last time and I'm kicked out to the login screen. :(

I'm thinking that what I want is some way to clear all the evaluated route models after a user logs in. If this was a multi-user system and one user logs out and another user logs in on the same computer without refreshing the page then that new user shouldn't have routes automatically resolved from the previous user's session.

This seems to me like it would be a common problem yet I can't find any sort of app-wide invalidate cache method. How should I solve this?

Community
  • 1
  • 1
  • Have you looked at how [ember-simple-auth](https://github.com/simplabs/ember-simple-auth) handles this? – Peter Brown May 09 '14 at 11:23
  • Why not check the session before the model so you are not blatantly calling a request that you know will get rejected? The model is not going to change unless the underlying context changes. – James_1x0 May 09 '14 at 14:20
  • @Beerlington, thanks for the suggestion about ember-simple-auth - I'll look into it. – Remy D'Agostino May 10 '14 at 04:56
  • @James_1x0, at the point where I am going to make a make an API request I have no idea if my session is authenticated. The only way I could do what you suggested would be by making an another API request in the beforeModel hook, which would mean waiting for a round trip to the API before any model is loaded. I don't want to do that because I want the app to feel fast. Also - this wouldn't solve my other problem where a new user could log-in and have routes with resolved models from a previous user. – Remy D'Agostino May 10 '14 at 04:57
  • @RemyD'Agostino So try changing the context when you call it. With a session token or something. – James_1x0 May 12 '14 at 16:28

2 Answers2

0

I'm not sure where ember data stands on the cache clearing feature, but here is one way to do it

clearCache: function (type) {
  var map = App.store.typeMapFor(type);
  map.idToCid = {};
  map.clientIds = [];
  map.recordArrays = [];
  map.findAllCache = null;
}

And here is an example as to how the ember firebase library handles a fail find using cache clearing.

  delete store.typeMapFor(store.modelFor('user')).idToRecord[username];

Full example here: https://github.com/firebase/emberFire/blob/master/examples/blog/js/app.js

  • Sorry Stephan, this doesn't work for me. My `App` object doesn't have a `store` property - probably because I'm not using ember-data and am managing my models manually. – Remy D'Agostino Jun 26 '14 at 23:51
0

For anyone else who finds this - I never found a way to reset the ember application and cause it to forget all resolved routes. I did find a few other work-arounds.

In the end, I opted to just window.reload() any time that a user logged out of the system or had their authentication token expire.

Authenticated URLs

Another reasonable approach would be to put a random unique id in the hash state. Essentially just do this.

Instead of a route like:

#/contacts/1

prefix every authenticated route with some kind of unique id

#/PyUE4E+JEdOaDAMF6CwzAQ/contacts/1

App.reset

I tried tried a number of things. One of the more promising things I tried was redirecting to the login screen and using the Application's reset method on my global App object. http://emberjs.com/api/classes/Ember.Application.html#method_reset

That didn't work though, it seems that even a reset Application remember's the models of any routes that it has resolved - weird.