Need help figuring out what this Ember unloadAll does - ember.js

I have this route that currently, when I transition back to it, gives me this great big error with a stack trace that doesn't help me figure out what is going wrong.
Error while processing route: project.details Assertion Failed: calling set on destroyed object Error: Assertion Failed: calling set
on destroyed object
at new Error (native)
at Error.EmberError (http://starqa.fmr.com/assets/vendor.js:22615:21)
at Object.Ember.default.assert (http://starqa.fmr.com/assets/vendor.js:15716:13)
at Object.set (http://starqa.fmr.com/assets/vendor.js:26367:22)
at exports.default.mixin.Mixin.create.set (http://starqa.fmr.com/assets/vendor.js:41034:20)
at Ember.Object.extend.flushCanonical (http://starqa.fmr.com/assets/vendor.js:69769:14)
at ember$data$lib$system$relationships$state$has_many$$ManyRelationship.flushCanonical
(http://starqa.fmr.com/assets/vendor.js:71525:22)
at Queue.invoke (http://starqa.fmr.com/assets/vendor.js:11425:18)
at Object.Queue.flush (http://starqa.fmr.com/assets/vendor.js:11490:13)
at Object.DeferredActionQue
Through just going through my routes and commenting out stuff, I found this in my projects route:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function(params) {
if (params.q) {
return this.store.find('project', params);
} else {
var _this = this;
Ember.run(function() {
_this.store.unloadAll('project');
});
return this.store.findAll('project', {reload: true});
}
}
And if I comment out lines 7-9:
/*
Ember.run(function() {
_this.store.unloadAll('project');
});
*/
then the error goes away and the transition worked. This bit of code was written by somebody else, and I think it has to do with refreshing the model from the store, but I can't figure out why it would cause this "calling set on a destroyed object" error.
Any help would be greatly appreciated.

Offhand it looks like this route serves both single project param (q is specified) and all projects, which offhand sounds like far from perfect design for ember. Ideally you should have two routes - the projects and project route. That's on the Ember architecture side.
On the functional side, this happens when you object was released by ember yet you try and access it a second time. I suspect this is happening as the Ember.run, runs the unload all in another loop, whereas I don't think that's what's needed. Try to remove the unload all from the ember loop, or most chances you can just run without it at all (as why do you want to remove all the object in the current cache each time you call the route?
this.store.unloadAll('project');
return this.store.findAll('project', {reload: true});
Or just leave it commented out (I think leaving it commented should work for you).

Related

Ember 3.0 acceptance redirect test hangs forever

I have a simple acceptance test written in the modern RFC 268 format for Ember 3.0.
The test is for a page where, if the user is unauthenticated, the URL immediately redirects to /login.
...
module('Acceptance | index', function (hooks) {
setupApplicationTest(hooks);
test('visiting / logged out', async function(assert) {
assert.expect(1);
// Test hangs here forever.
await visit('/');
assert.equal(currentURL(), '/login');
});
});
This test worked great using the older format with moduleForAcceptance.
Unfortunately, this test hangs forever in Ember 3.0. Am I missing something here? Is there a better way to test a redirect?
There are no errors in the console, and the addition of some console.log statements show that the await is where the test hangs.
I found the reason why this was failing. I have an Ember mixin that I use to enhance all of my routes. The mixin checks for whether or not a user is authenticated, and redirects to /login as needed.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
});
}
});
You'll notice that I am not resolving if authenticated is falsey. That worked fine with my app and the test syntax in 2.18.
The docs say the following regarding that hook I am overriding in my mixin.
returns Any | Promise
if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way.
To me, the bit about "non-promise return values" implies that I should be able to do what I'm doing. Especially considering this worked in 2.18, but I wonder if this was one of those "wow, how did that ever work in the first place" scenarios. Clearly this syntax isn't working in 3.0. since the transition pauses forever when testing.
The answer for me was to ensure I always resolve/reject something. In this case, I had to add an explicit reject() so that the promise chain doesn't hang.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
reject();
});
}
});
My test was fine. It was the mixin that needed updating in order to work properly with Ember 3.0 and the latest testing syntax.
The issue was not what your beforeModel hook resolves if user is authenticated but that your Promise does not resolve at all. You don't have to return a Promise in beforeModel hook but if you return one it will block the transition until the Promise is resolved. Since it's not clear how ember should react if another transition is called while current transition is blocked (not resolved/rejected promise), resolving or rejecting is correct behavior. Please have in mind that in a Promise return does not have any other meaning than ending your execution. It does not resolve or reject your Promise.
Another reason visit() could hang is that it waits for timers like Ember.run.later() to resolve, causing a non-obvious block somewhere in the application.
AlphaGit on github summarized the issue with an example, saying:
Most of the actions that Ember.testing executes (like visit) will
append to a promise that gets executed action after action in the
right order. In order to pass to the next action, Ember.testing makes
sure that there is nothing pending, so that the step can be considered
complete and move forward.
Along with the things that are tested for, pending AJAX requests are
verified, and also scheduled timers. These timers may arise from, you
guessed it, Ember.run.later calls. If for any reason you would have in
your code periodic Ember.run.later methods (so that one is always
waiting to be excuted), it's likely that you'll face this issue.
I've faced it myself in a similar scenario: My server returns a OAuth
access token with 100 hours until expired, so ember-simpleAuth
registers a call close to the expiration time with Ember.run.later to
refresh the token. This will, however, prevent the test from moving
along. My specific situations has been fixed in further versions but
any similar behavior will reproduce the issue (which is likely a
conclusion of the current design of Ember.testing).
Here are a couple other examples of users running into similar issues:
https://stackoverflow.com/a/58526993/3257984
https://stackoverflow.com/a/27887807/3257984

Nested routes: handling failed child find in error action loses parent context

I've recently been porting a number of Ember apps I maintain to RC 8 and ran into this.
Before the router facelift landed I would sometimes manage control flow via promises returned by Ember Data find calls.
For example:
SomeRoute = Ember.Route.extend({
model: function(params) {
var resolve = function(model) { return model; };
var route = this;
var reject = function() { this.transitionTo('someOtherRoute'); };
return SomeModel.find(params.some_model_id).then(resolve, reject);
}
...
});
With the recent changes, it is now possible to handle errors created in model callbacks via the error action:
SomeRoute = Ember.Route.extend({
// note: model callback no longer needed--default suffices
actions: {
error: function(reason, transition) {
// check the reason object to determine how/if to handle this error
this.transitionTo('someOtherRoute');
}
}
...
});
I much prefer the latter approach as it makes the code easier to read and better separates concerns.
This works well in most cases but I encountered an issue in an app that uses nested routes. I've included a simplified example followed by a jsbin that demonstrates the issue.
Lets say we want to show Articles that belong to Authors and the URLs look like: /authors/:author_slug/articles/:article_slug. We want to redirect to a Not Found page when someone tries to view an article that doesn't exist.
When managing control flow in the model callback as above, you can browse to /authors/some_author/articles/some_invalid_slug and be redirected to /authors/some_author/articles/not_found as expected.
However, if the redirect to the Not Found page is instead managed via the error action, the parent context is lost at some point and you end up at /authors/undefined/articles/not_found.
You can see this in the following jsbins:
http://jsbin.com/eJOXifo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/eJOXifo/1#/authors/schneier/articles/not_found)
http://jsbin.com/oNaWelo/1#/authors/schneier/articles/12345
(redirects to http://jsbin.com/oNaWelo/1#/authors/undefined/articles/not_found)
Does anyone know why this happens or how to avoid it?
Notes:
I know this doesn't have anything to do with Ember Data. However, implementing something equivalent without Ember Data just makes the example more complicated without adding anything
There are a few small hacks to make Ember Data work as expected in jsbin:
I'm preloading the parent model to avoid having to load it from anywhere.
I'm not doing anything special to provide data for the child model. The app just makes a request to http://jsbin.com/articles/12345. This actually returns a 200 but bombs anyway because the response is html. An API that correctly returns a 404 response gives the same behvaiour.
I remember a while ago reading about some service that could be used to build fake API responses for use with services like jsfiddle or jsbin. If anyone knows what it is please comment.
Your right that the parent context is getting lost. The trick is to extract that context from the transition and pass it as an argument when calling transitionTo. So:
App.ArticlesArticleRoute = Em.Route.extend({
actions: {
error: function(reason, transition) {
console.log('in error handler');
this.transitionTo('articles.notFound', transition.resolvedModels.authors);
}
}
});
With this change, visiting the url:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/my-fake-article
will redirect to:
http://jsbin.com/iVOYEvA/2#/authors/schneier/articles/not_found

how to really find out only once if Ember finishes its ajax request and DS.RecordArray is completely filled

I have probably tried a gazillion ways and nothing is efficient. My last attempt - which works a little well but with an ugly tradeoff was this:
App.UsersRoute = Em.Route.extend({
model: function() {
return App.User.find({}).then(function(response) {
return response;
});
}
});
the problem with this - which I'd love to know is its making a synchronous call. My HTML/DOM won't finish loading until this returns.
Another thing I'd love to know is if I omit the empty object {} from the find - the promise function actually gets called immediately. I promise!
Now other methods I've tried are the following which all have flaws:
observing content.lastObject.isLoaded on a controller
implementing arrayContentDidChange from Ember.ArrayController - obviously this gets triggerd multiple times as the array is getting filled.
-
I have probably tried a gazillion ways and nothing is efficient.
I don't know if you tried hooking into the afterModel function of a route which is an addition added not very long ago and available in rc6:
App.UsersRoute = Em.Route.extend({
afterModel: function(users, transition) {
console.log(users.get('length'));
}
});
See here for more info on the hooks beforeModel and afterModel.
I've also put togheter a jsbin to play around.
Hope it helps.
You can read about it here https://github.com/emberjs/data/pull/735
There is a question like yours Ember-data RecordArray isLoaded Status.
Also you can try to observe isUpdating to be false instead of isLoading

How to handle 404 of Ember Data in route?

In my route I have a method that tries to request a list of models from the server
model: ->
App.MyModel.find
projectId: (#modelFor "project").id
Now obviously sometimes this might return a 404.
At the moment when this happens, Ember just stops doing anything. No view is rendered, no controller is setup.
So how can I properly handle the 404 (ie show an error view)?
Bad news: right now, ember-data doesn't do anything when it gets a 404 on find(). At all. The model sits in the 'loading' state forever.
There are no non-completely-stupid options, here, in my opinion. What I would probably do as a last resort is add a notFound attribute on my DS.Model, and instead of returning 404, return JSON with notFound set to true. It's painful, I know...
--- I had originally offered a solution of overriding find in a subclass of RESTAdapter. Then I noticed that find DOES NOT get passed the record instance it is supposedly loading. So, no go on handling 404s by putting the record into an error state.
[NOTE: ember-data has changed dramatically since March 2013, the information in this answer may no longer be operative]
Incidentally, the "new" BasicAdapter was just released now. The question for me was, will this make things easier to handle 404 errors.
Approach #1
My first approach - similar to what Christopher was suggesting - was to add an additional field containing the HTTP status.
status: DS.attr("number");
And then I used this AJAX call:
$.getJSON(url, data).then(null, function(xhr) {
return {
id: id,
statusCode: xhr.status
};
}).always(function(data) {
return process(data).load();
});
What this does is to transform the error response (xhr) to a hash containing the requested id and the status code. Finally, the successful result or the failed hash are passed to the store.
This kind of works, but isn't very practical: When you show a list of all model instances those "mock" instances have to be filtered out manually.
Approach #2
Another idea was to create a special error model.
App.Error = App.Model.extend({
status: DS.attr("number")
});
And the according query:
$.getJSON(url, data).then(null, function(xhr) {
return App.store.load(App.Error, {}, {
id: 0,
status: xhr.status
});
}).done(function(data) {
return process(data).load();
});
This will load and create a new instance of the error model and put it into the store.
The problem with this is that Ember wasn't really "accepting" this. The application just stopped routing, doing nothing anymore. So this seems like a dead end as well :(
I hit this issue as well today.
However, after looking at the source, it appears the model is actually setup to utilize Ember.Evented, and we can add our own handlers for these cases.
The two events that caught my eye were becameError and didLoad.
In my case I was able to do something like the following:
// Grab a user by id.
var user_rec = App.User.find( user.id );
// Oh no! Error!
user_rec.on('becameError', function() {
alert('I could not find you!');
});
// We should be good! Proceed!
user_rec.on('didLoad', function() {
alert('Your email: '+this.get('email'));
});
Here's the source on github: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/model/model.js
Hopefully, if this is indeed the way we should be handling things, there will be some added documentation in the guides in the near future.

Ember.js 1.0-pre4 + jQ UI sortable + localstorage adapter

Day 2 learning ember.js...
I'm working on a offline app that needs to save draggable/sortable tile positions to localstorage, and if there is no existing data, load & save from a fixture.
Using: ember 1.0.0-pre4, ember-data rev11, ember-localstorage-adapter, jQ 1.9, jQ UI 1.9
https://github.com/rpflorence/ember-localstorage-adapter
It's working, but I'm a bit of a novice, feel it's not pretty and could use some community advice.
http://jsfiddle.net/Nsbcu/4/
Questions
What is the proper way to check if your DS.Store has loaded and is empty? My method of looking directly at localstorage didn't feel right.
After I createRecords from the App.Tile.DEFAULTS I feel I should commit them, but an error is thrown. I don't have to commit the known defaults, but curious what causes the error and how I should go about committing properly. Also is the App.ready() callback the right place for loading defaults? Error only happens when localstorage is empty
Uncaught Error: Attempted to handle event loadedData on <App.Tile:ember231:1> while in state rootState.loaded.created.inFlight. Called with undefined
On the TilesController I'm using sortProperties which works great until jQ UI Sortable changes the DOM and Ember wants to update my tile order, before I get a chance to set the new order. My current solution is to turn off sortProperties temporarily while updating the model. Again this feels hacky, suggestions on proper way to do this?
=== Edit Feb 3 ===
If I do an async commit the initial error in question #2 is avoided.
App.TilesRoute = Ember.Route.extend({
model: function() {
return App.Tile.find();
},
setupController: function(controller) {
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
// Commit async, else generates error
var _this = this;
setTimeout(function() {
_this.store.commit();
}, 1);
}
}
});
I would put any initial code inside the application or the index Route within the setupController method
if (localStorage.getItem('fusion-emberjs') == null) {
App.Tile.DEFAULTS.forEach(function(item) {
App.Tile.createRecord(item);
});
//*** WARNING: Generates Error ***/
App.Tile.find().get('store').commit();
}
Once you move the code inside the route, replace App.Tile.find().get('store').commit(); by App.store.commit() inside your route
Create your own transaction instead of using the default one, each time you make a call to the store directly you're using the default transaction. You can create a transaction this way
var transaction = App.store.transaction()
transaction.createRecord(App.Foo);
transaction.commit()
transaction.rollback();
Any call to App.store assumes you already created a store, right now you're only extending the DS.Store. Try instead
App.Store = DS.Store.create({
revision: 11,
adapter: 'App.LSAdapter'
});
I would suggest that you do any event handling or transaction management in the router unless it's purely for styling or animation. In that case, the view is the right place for it. I like the router to orchestrate communication between all the assets (controllers, routes, models, views)
A good pattern to remember is a view talks only to a controller, a controller is a mere proxy to a model, a router orchestrates communication between controllers and manages routes.