ember data and dealing with 404 - ember.js

There have been questions about this topic in previous years, but Ember has changed a lot since then and most of those answers were fill ins until things were more 'together'. I am working on an app that is using ember-cli. On the api, if you request a resource from the api that does not exist, the api returns a 404. However, ember data seems to just throw an error upon receiving a 404.
I saw one approach that seemed promising, someone answered a similar question in 2014 and had this code sample:
return this.store.find('matter', params.matter_id).then(
(function (_this) {
return function(model){
resolve(model);
}
})(this),
(function (_this) {
return function(invalid){
_this.transitionTo('auth.denied');
}
})(this));
ember promises can take a resolve and reject as arguments. In the above code, he passed self instantiating functions as the resolve and reject arguments. The reject is working just as I would like it to. However, now the issue that I am running into is that when I am in the resolve, even though the 'model' variable comes back with an ember data object, i cannot seem to get this to resolve properly. Ember throws and error stating, "Expected an object as data in a call to push for matter , but was undefined".
I was hoping someone out there in the Ember community might have some insight in either how to get this to resolve properly, or perhaps a better way to approach this problem altogether.

this.store.find returns a promises.
Try doing something like
this.store.find(...).then(function(model) {
console.log(model);
resolve(model)
}, function(reason) {
alert('error');
});
Is the model json, if not it's probably the problem?

Related

Need help figuring out what this Ember unloadAll does

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).

Ember 'currentUser' Model/Controller Setup

Ok, so here's what I'm trying to accomplish (in ember.js):
New model/controller to manage the current user and session information
The model needs to be available everywhere so I can just do something like currentUser.firstname (for instance, in the nav)
After lots and lots of research, it seems that setting up a separate controller/model is the best way to go. I tried doing everything in the application controller, but then I need to implicitly set the user model (somehow?) on that controller, which doesn't seem like a good idea (what if I need to do other things in the application controller?).
So here's what I've tried:
controllers/session.js
init: function() {
// this never gets called unless I call the setCurrentUser
// function from another controller using 'needs' or something
},
setCurrentUser: function() {
// you can ignore the authData.uid variable - it's something used
// by firebase, but not important for this example. Just assume I'm
// requesting and getting a user back.
this.store.find('user').then(function(users) {
this.set('currentUser', users.filterBy('uid', authData.uid)[0])
}
}
models/session.js
DS.Model.extend({
currentUser: DS.belongsTo('user')
});
I don't have a route or view/template associated with this model/controller because it really wouldn't make sense (the user doesn't need to see a page all about him/herself).
So I tried calling setCurrentUser from the application controller by doing something like this.get('setCurrentUser')() (which looks very weird - there's got to be a better way, but I think the answer may be not calling this from the application controller and initializing the controller in a different way?).
I would love some advice on how I can get this working. Sorry that I'm trying to develop and explain the architecture of the app all at the same time, so it is a bit messy - please let me know if anything is unclear.
Thanks for the help!

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 can I know the error reason in promise's rejection handler?

I wrote some code using find method like below.
The adapter is RESTAdapter.
App.SessionManager = Em.Object.extend({
userIdChanged: function() {
App.User.find(this.get('userid')).then(
function(user) {
/* process something */
},
function(error) {
/* rejection handler */
/* I want to know what error returned by the server here.*/
/* but how? */
}
);
}.observes('userid'),
});
If my server returned some error(e.g. 401) the rejection handler called.
But it seems that the argument of handler doesn't have error information.
How can I know the error reason in rejection handler?
Handling of errors returned by the server is not yet fully implemented in ember data.
For reference, as stated in this blog post about ember-data:
We want to make error handling and dealing with client and server conflicts rock solid. A pull request from Paul Chavard is currently open and looks like a solid starting point for error handling. You should see much more development on this in the near future.
But there are some workaround you can do to get to that information in the meanwhile.
For example:
Ember.RSVP.configure('onerror', function(error) {
console.log(error.message);
console.log(error.stack);
});
Or use a dirty hack to get to that information. Since ember-data uses jQuery under the hood you can subscribe to ajaxError:
$(document).ajaxError(function(event, jqXHR, ajaxSettings, thrownError) {
// this will trigger anytime there is an error
});
Hope it helps.
you can also override this:
becameInvalid: function(record) {
// you can get errors just by doing record.errors, error keys must match
// the name of a field that you have defined in your model
}
and:
becameError: function(record) {
}
that's in case something happened in the server that caused an error. I just saw that you want to handle errors on promises, well, that's something different that I haven't dealt with. becameError might be fired.
UPDATE:
I just found something interesting that might help you: Ember-Data handling 401’s thanks to the new router, I haven't had the chance to use all those new features, but they look pretty cool.

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.