I'm starting a project working with ember.js with Django Rest Framework for my REST Server.
I'm using an adapter on the ember side to get the data into the right format that Ember needs. I'm at the point where I'm making a request to my server, and getting a 200 with data returning, however Ember is throwing an error Error while processing route: index Assertion Failed: The response from a findAll must be an Array, not undefined.
There are quite a few posts about this error, but I haven't been able to find any that are relevant to my problem, it seems to be a sort of blanket error that can be caused by many things?
I'm at a loss because my server is receiving the request and returning data in the format that I would expect from the server. I'm not sure how to tell if the error is within my Ember app or if my data is not being transformed correctly by the adapter.
// routers/index.js
export default Ember.Route.extend({
model: function() {
return this.store.find('user');
}
});
// config/environment.js
APP: {
API_NAMESPACE: 'v1',
ENV.APP.API_HOST = 'http://localhost:8000',
},
Just having this code in my router is how I get the error. I feel like this is a very simple problem, but can't seem to get past it. I've tried moving that code into different parts such as the controller, just to see that I can fetch data. Hopefully someone has a suggestion, thanks. Let me know anything else I can post to help.
EDIT: Going through the source it appears there is a problem in the serializer:
extractArray: function(store, type, payload) {
// Convert payload to json format expected by the RESTSerializer.
// This function is being overridden instead of normalizePayload()
// because `results` will only be in lists.
console.log(type);
var convertedPayload = {};
if (payload.results) {
convertedPayload[type.modelName] = payload.results;
} else {
convertedPayload[type.modelName] = payload;
}
return this._super(store, type, convertedPayload);
},
The type.modelName is undefined. When I look at the type object, it's just a blank Class(). When i log type.toString() I get joe2go#model:user:
Could it be something related to my model? Everything seems pretty standard to me.
import DS from 'ember-data';
export default DS.Model.extend({
username: DS.attr('string'),
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
There is apparently a bug in the package.
extractArray: function(store, type, payload) {
// Convert payload to json format expected by the RESTSerializer.
// This function is being overridden instead of normalizePayload()
// because `results` will only be in lists.
var convertedPayload = {};
if (payload.results) {
convertedPayload[type.modelName] = payload.results;
} else {
convertedPayload[type.modelName] = payload;
}
return this._super(store, type, convertedPayload);
},
Inside the serializer it refers to type.modelName, but that should be type.typeKey
Related
I am using ember-cli and ember-data 1.13.7 and JSONAPIAdapter.
I use http-mock to mock data during local testing.
It worked well when I used the RESTAdapter, but I ran into a problem when switching to the JSONAPIAdapter.
The problem is that the records data does not get loaded into the store and I get an error reading an undefined property.
The adpater just looks like this:
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
namespace: 'api',
});
The ajax call is this:
http://localhost:4200/api/users/1
The http-mock looks like this:
usersRouter.get('/:id', function(req, res) {
res.send({
'users': {
id: req.params.id,
firstname: "John",
lastname: "Doe",
email: "johndoe#example.com",
mobile: "12345678",
nextAppointment: 1
}
});
});
The response looks like this:
{"users":{"id":"1","firstname":"John","lastname":"Doe","email":"johndoe#example.com","mobile":"12345678","nextAppointment":1}}
The response data islooking good but the problem is that together with the response is the header status code of 304, and the fact that the data is not loaded into store. The object with id=1 is created, but all properties in the object are 'undefined' when I look at the stores data in the ember inspector.
Update:
The error I get is:
Error while processing route:
home.index Cannot read property '_internalModel' of undefined
TypeError: Cannot read property '_internalModel' of undefined
Update 2:
It turns out the 304 is not important. The model is still not properly loaded into store when the httpcode is 200 either.
I also fund that this call works fine:
http://localhost:4200/api/users
while this call fails:
http://localhost:4200/api/users/1
They return exactly the same JSON response.
Your call for:
http://localhost:4200/api/users
can't return exactly the same JSON response.
Your call for: http://localhost:4200/api/users/1 should return:
{
"data": {
"id":"1",
"type": "user",
"attributes" : {
"firstname":"John",
"lastname":"Doe",
"email":"johndoe#example.com",
"mobile":"12345678",
"nextAppointment":1
}
}
}
Read more about JSONAPIAdapter:
JSON API ADAPTER AND SERIALIZER in Ember Data v1.13 blog post
jsonapi.org
I'm getting a really opaque error message (opaque in the sense I have no point of reference for my own source) from console, I'm not entirely sure where to look, I feel it's likely an error in library code but before posting this on github I'll just double check it's not my own fault.
The Problem
The Problem is simple, I'm calling this.store.find('player'), in hopes to get a list of all players, and then display them in some kind of list, but I'm not even getting past the loading part. The data is pulled from the server and looks properly formatted, but something seems to be failing after the route.model method call. And the error message seems to be somewhere in the ember.js library code with nothing pointing back to my own code.
Server Response
The content type is of course application/json, and note the id property is actually _id.
[
{
"_id":"55405a5102b4ed623c225e87",
"alias":"mikeTest",
"__v":0,
"scans":[],
"createdAt":"2015-04-29T04:13:05.223Z"
}
]
Error message
Note there is part of the stack trace pointing to my source, only Ember source. Which has made this a pain to debug.
Error while processing route: leader Cannot read property 'match' of undefined TypeError: Cannot read property 'match' of undefined
at Ember.DefaultResolver.extend.podBasedComponentsInSubdir (http://localhost:4200/assets/vendor.js:60138:76)
at http://localhost:4200/assets/vendor.js:60190:34
at Array.exports.default.mixin.Mixin.create.find (http://localhost:4200/assets/vendor.js:39572:30)
at Ember.DefaultResolver.extend.findModuleName (http://localhost:4200/assets/vendor.js:60188:44)
at resolveOther (http://localhost:4200/assets/vendor.js:60051:37)
at superWrapper (http://localhost:4200/assets/vendor.js:28141:20)
at exports.default.EmberObject.default.extend.resolve (http://localhost:4200/assets/vendor.js:15454:35)
at Object.resolve [as resolver] (http://localhost:4200/assets/vendor.js:15217:23)
at resolve (http://localhost:4200/assets/vendor.js:12792:29)
at Object.Registry.resolve (http://localhost:4200/assets/vendor.js:12336:21)
Source
This ember app is very young, so there is very little source at the moment, but this is all the relevant source at the moment.
Routes
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
export default Router.map(function() {
this.resource('leader');
this.resource('profile');
this.route('loading');
});
Leader route
Leader has a template and a controller, but they are basically empty right now.
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
return Ember.RSVP.hash({
players: this.get('store').find('player')
});
},
});
Player Model
import DS from 'ember-data';
export default DS.Model.extend({
alias: DS.attr('string'),
createdAt: DS.attr('date'),
scans: DS.hasMany('scan'),
});
Application Adapter
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
namespace: ''
});
Application Serialiser
import DS from 'ember-data';
export default DS.RESTSerializer.extend({
primaryKey: function (type) {
return '_id';
},
serializeId: function(id) {
return id.toString();
}
});
Versions
I'm not sure if any of the versions here are particularly important
ember-cli is 0.2.3
ember-data is 1.0.0-beta.16.1
ember is 1.11.1
Things I've tried
removing properties from the model, in the event the relationships seemed to be the problem (nothing changed)
tried setting up a serialiser and adapter for the application (included above), nothing changed.
the serialiser in the event that the id field in the response is actually _id.
tried updating ember data, nothing changed.
Okay I figured out what was being done wrong... I forgot to check if the data being returned by the server abides to the convention/protocol required to use ember data. The JSON returned by the server looks like this.
[
{
"_id":"55405a5102b4ed623c225e87",
"alias":"mikeTest",
"__v":0,
"scans":[],
"createdAt":"2015-04-29T04:13:05.223Z"
}
]
It should actually look like this
{
"players": [
{
"_id":"55405a5102b4ed623c225e87",
"alias":"mikeTest",
"__v":0,
"scans":[],
"createdAt":"2015-04-29T04:13:05.223Z"
}
]
}
So yes this was me being dumb and missing something.
Why is this Required
Ember data expects JSON returned from the server to meet the JSON API Standard, which is a standard that specifies the formatting of the JSON returned from a server. In this case, the data didn't meet the JSON API standard, as I forgot to put the array of players under a key called players. There are some more examples of this in the Ember v1.10.0 guide to models.
The reason Ember Data expects this is so Ember Data can make the certain assumptions about the data returned from the server.
I have a basic create form setup which has validation on the name field. The save action for the form has a promise to gracefully hold the error:
actions: {
save: function(){
var route = this;
var createCampaign = this.store.createRecord("campaign", {
code: this.get("code"),
name: this.get("name"),
description: this.get("description"),
});
this.set("code",""),
this.set("name",""),
this.set("description",""),
// POST values to campaigns
createCampaign.save().then(function(c){
route.transitionToRoute("campaigns.view",c.id);
}, function(errors){
});
}
}
});
My defining attributes is:
TM.Campaign = DS.Model.extend({
name: DS.attr(),
code: DS.attr(),
description: DS.attr(),
});
I've read that by using a RESTAdapter, the ajaxError needs to be overwritten, so I've added the following:
ajaxError: function(jqXHR){
var error = this._super(jqXHR);
if(jqXHR && jqXHR.status === 422){
var response = Ember.$.parseJSON(jqXHR.responseText);
errors = {}
if (response.errors){
var jsonErrors = response.errors;
Ember.keys(jsonErrors).forEach(function(key){
errors[Ember.String.camelize(key)] = jsonErrors[key]
});
}
return new DS.InvalidError(errors)
} else {
return error
}
}
The response which is coming from the API is structured as:
{
"errors": {
"name": [
"The name field is required."
]
}
}
But for some reason, whenever I try to display DS.Errors (using console.log(route.get("errors")), I get undefined. It's like Ember doesn't know that a validation error has appeared within the response.
I've also made sure that the response status comes back as 422 Unprocessable Entity. Can anyone see what I'm missing??
EDIT: I have been able to create a JS Bin to demonstrate my problem: http://jsbin.com/xujari/2/
Your jsBin slightly modified :
http://jsbin.com/luwaqeyoca/1/edit?js,console,output
The description of DS.InvalidError never state about setting an error in the route. The sure part is that it sets the promise rejection error. It's also set an error property on the record it self if the property has a property maching the error key name (in breafe if your error key is name you HAVE TO have a property name in your model)
createCampaign.save().then(function(c){
route.transitionToRoute("campaigns.view",c.id);
}, function(errors){
console.log(errors.errors.name); //from the promise error
console.log(createCampaign.get("errors.name")); //from your record
});
EDIT
Here is a bin modified as you asked in comments
http://jsbin.com/sokebuzumu/1/edit?html,js,output
you need to create an empty object "errors" in your controller as in the jsbin
createCampaign.save().then(function(c){
}, function(errors){
this.set("errors",createCampaign.get("errors"));
}.bind(this));
I just answered a similar question on Ember Data Error handling here which covers a number of aspects to error handling related to your question.
You don't need to override the ajaxError handling on the adapter any more since my PR was merged into Ember Data. Errors will be applied to the model correctly now so you can refer to errors using the functions as per my previous answer.
I'm not sure how to store json data into a model in the controller. Here is my code.
App.LoginController = Ember.ObjectController.extend({
actions: {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
return data;
} else {
}
});
}
}
});
after the line if(data.isFail) {.... I want to store the json data into a model. How do I do this?
UPDATE
I went with Josh's suggestion of putting the action into the Route
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var data = this.getProperties("email", "password");
console.log(data);
return $.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
store.createRecord('login', data);
} else {
}
return data;
});
}
}
});
No I'm getting an error Uncaught TypeError: Cannot read property 'normalize' of undefined.
If you have defined a DS.Model and if the contents of your JSON are a subset of the properties defined in your DS.Model then you can just do this:
store.createRecord('my-model-name', data);
This assumes that you have a DS.Model defined called my-model-name.js and that your JSON is in the data var. If you have some properties in your JSON that are not defined in your DS.Model, I'm not sure how Ember Data reacts.
UPDATE: You asked about how to connect the route and the controller. You can use this idiom:
Ember.Route.extend({
setupController: function(controller, model) {
this._super(controller, model);
controller.set('myProperty', 'hello');
controller.set('meta', this.store.metadataFor('org-user'));
}
})
Note that this doesn't really relate much to your original question.
UPDATE #2:
Ok, it looks like you want to attempt a login, and then if the POST request succeeds, but the login itself fails (as indicated by the presence of an isFail property in the json response), then you want to create a new record in your local store? Did I describe your intentions right?
My first question is, are you sure you want to create an Ember record here? An Ember record is basically a "facsimile" of a "real" object that comes from your backend / database. It makes sense to create a new local record if you want to eventually persist that somewhere (e.g. by calling myRecord.save). Maybe you would create a local record for cacheing purposes only, but I personally have not seen that in the wild yet (but don't let me disqualify your usage if you've thought it through).
With that out of the way, let's assume you DO want to create a local record. Then first we actually need a DS.Model that represents the record this will be. I'll define mine like this:
models/login.js
DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string')
});
Note that I made sure to copy your properties from this.getProperties() because the way you're initializing your model with createRecord('login', data) means that what's in data needs to be a subset of what I just defined above.
Now that I have a model definition, I an get instances of this model from a backend via JSON (or any other format as long as I have the right serializer; Ember by default uses `RESTSerializer, which expects JSON). I can also locally instantiate a new model, or in Ember speak, create a record. Again, my goal in creating a new record is probably that I eventually want to persist it to my backend. But since you're doing your own AJAX calls, I'll leave that part out.
Now onto your code, with slight revisions:
App.LoginRoute = Ember.Route.extend({
actions : {
login: function() {
var _this = this;
var data = this.getProperties("email", "password");
$.post('/', {
email: data.email,
password: data.password
}).then(function(data) {
if(data.isFail) {
var loginModel = _this.store.createRecord('login', data);
loginModel.save(); // not sure if you want to do this?
} else {
}
});
I took out the return statements because I'm guessing you don't need them (I may be wrong). I also decided I would do something with the model instance we just created, in this case save() it, which will trigger a POST request to your backend, as determined by your adapter (by default Ember uses RESTAdapter to determine this).
Note also that I needed access to the current route instance via this but this takes on different meanings as I descend down the code, so I define var _this = this; at the top so I can reference the "real" this when I need it.
Does this solve your issue?
I'm using Ember Data and I can't seem to get the model's 'errors' property to populate with the error messages from my REST API. I'm pretty much following the example at this guide:
http://emberjs.com/api/data/classes/DS.Errors.html
My app looks like this:
window.App = Ember.Application.create();
App.User = DS.Model.extend({
username: DS.attr('string'),
email: DS.attr('string')
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('user', {
username: 'mike',
email: 'invalidEmail'
});
},
actions: {
save: function () {
this.modelFor(this.routeName).save();
}
}
});
And my API returns this:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 125
{
"errors": {
"username": ["Username is taken!"],
"email": ["Email is invalid."]
}
}
After I call save() on the model, here is what I see on the user model:
user.get('isError') // true
user.get('errors.messages') // []
Even though the model is registering the isError property correctly, I can't seem to get the error messages to populate. How can I get this to work? I'm working on the latest beta build of Ember Data version 1.0.0-beta.8.2a68c63a
The docs are definitely lacking in this area, the errors aren't populated unless you're using the active model adapter.
Here's an example of it working, also check out Ember: error.messages does not show server errors on save where I say the same thing
http://jsbin.com/motuvaye/24/edit
You can fairly easily implement it on the RESTAdapter by overriding ajaxError and copying how the active model adapter does it.
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
http://jsbin.com/motuvaye/27/edit
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/activemodel-adapter/lib/system/active_model_adapter.js#L102
I've had a long and very frustrating experience with Ember Data's errors.messages property, so I thought I'd summarize all of my findings here in case anyone else tries to use this feature.
1) Documentation is out of date
As #kingpin2k mentioned in his answer, the documentation at http://emberjs.com/api/data/classes/DS.Errors.html is out of date. The example they provide on that page only works if you're using DS.ActiveModelAdapter. If you're using the default DS.RESTAdapter, then you need to do something like this. Note that I prefer this simpler approach instead of just copying ActiveModelAdapter's ajaxError implementation:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
this._super(jqXHR);
var response = Ember.$.parseJSON(jqXHR.responseText);
if (response.errors)
return new DS.InvalidError(response.errors);
else
return new DS.InvalidError({ summary: 'Error connecting to the server.' });
}
});
2) You must supply a reject callback
This is very strange, but when you call save() on your model, you need to provide a reject callback, otherwise, you'll get an uncaught 'backend rejected the commit' exception and JavaScript will stop executing. I have no idea why this is the case.
Example without reject callback. This will result in an exception:
user.save().then(function (model) {
// do something
});
Example with reject callback. Everything will work well:
user.save().then(function (model) {
// do something
}, function (error) {
// must supply reject callback, otherwise Ember will throw a 'backend rejected the commit' error.
});
3) By default, only the error properties that are part of the model will be registered in errors.messages. For example, if this is your model:
App.User = DS.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
...and if this is your error payload:
{
"errors": {
"firstName":"is required",
"summary":"something went wrong"
}
}
Then summary will not appear in user.get('errors.messages'). The source of this problem can be found in the adapterDidInvalidate method of Ember Data. It uses this.eachAttribute and this.eachRelationship to restrict the registration of error messages to only those that are part of the model.
adapterDidInvalidate: function(errors) {
var recordErrors = get(this, 'errors');
function addError(name) {
if (errors[name]) {
recordErrors.add(name, errors[name]);
}
}
this.eachAttribute(addError);
this.eachRelationship(addError);
}
There's a discussion about this issue here: https://github.com/emberjs/data/issues/1877
Until the Ember team fixes this, you can work around this problem by creating a custom base model that overrides the default adapterDidInvalidate implementation, and all of your other models inherit from it:
Base model:
App.Model = DS.Model.extend({
adapterDidInvalidate: function (errors) {
var recordErrors = this.get('errors');
Ember.keys(errors).forEach(function (key) {
recordErrors.add(key, errors[key]);
});
}
});
User model:
App.User = App.Model.extend({
firstName: DS.attr('string'),
lastName: DS.attr('string')
});
4) If you return DS.InvalidError from the adapter's ajaxError (the one we overrode above), then your model will be stuck in 'isSaving' state and you won't be able to get out of it.
This problem is also the case if you're using DS.ActiveModelAdapter.
For example:
user.deleteRecord();
user.save().then(function (model) {
// do something
}, function (error) {
});
When the server responds with an error, the model's isSaving state is true and I can't figure out to reset this without reloading the page.
Update: 2014-10-30
For anyone who's struggling with DS.Errors, here's a great blog post that summarizes this well: http://alexspeller.com/server-side-validations-with-ember-data-and-ds-errors/
UPDATE: Ember Data 2.x
The above response are still somewhat relevant and generally pretty helpful but are now outdated for Ember Data 2.x(v2.5.1 at time of this writing). Here are a few things to note when working with newer versions of Ember Data:
DS.RESTAdapter no longer has an ajaxError function in 2.x. This is now handled by RESTAdapter.handleResponse(). You can override this method if any special handling or formatting of errors is required. RESTAdapter.handleResponse source code
The documentation for DS.Errors and DS.Model.errors(which is an instance of DS.Errors) is currently a little misleading. It ONLY works when errors in the response adhere to the JSON API error object specification. This means it will not be at all helpful or usable if your API error objects follow any other format. Unfortunately this behavior can't currently be overridden well like many other things in Ember Data as this behavior is handle in private APIs inside of Ember's InternalModel class within DS.Model.
DS.InvalidError will only be used if the response status code is 422 by default. If your API uses a different status code to represent errors for invalid requests you can override RESTAdapter.isInvalid() to customize which status codes(or other part of an error response) to check as representing an InvalidError.
As an alternative you can override isInvalid() to always return false so that Ember Data will always create a more generic DS.AdapterError instead. This error is then set on DS.Model.adapterError and can be leveraged as needed from there.
DS.AdapterError.errors contain whatever was returned on the errors key of the API response.