I have a basic Ember app and I am trying to handle validation errors on save (model is using the REST Adapter). In my route I am doing:
task.save().then(
function() {alert("success");},
function() {alert("fail");}
).catch(
function() {alert("catch error");}
);
When the record is valid I get the "success" alert, but when record is invalid, I do not get the "fail" alert OR "catch error". In the console I get:
POST http://localhost:8080/api/tasks 422 (Unprocessable Entity)
Error: The adapter rejected the commit because it was invalid
The response from the api look like this:
{"errors":{"name":["can't be blank"],"parent_task":[]}}
I am using Ember Data 1.13.
You need to extend your adapter to handle the errors, the REST Adapter does NOT do this for you (only the Active Model one)
Something like this:
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;
}
}
});
Related
I am setting a model in CustomerRoute's setupController hook as follows:
var self_controller = controller;
var onSuccess = function(customer) {
var reloadSuccess = function(customer_reloaded) {
// customer is reloaded successfully
self_controller.set('model', customer_reloaded);
};
var reloadError = function(err_response_for_reload) {
if(err_response_for_reload.status == 401) {
terminate_session();
}
else {
HTTP_error();
self_controller.transitionTo('all_customers');
}
};
// reload Model to forcefully fetch from server
customer.reload().then(reloadSuccess).catch(reloadError);
};
var onError = function(reason) {
if(reason.status == 401) {
terminate_session();
}
else {
HTTP_error();
self_controller.transitionTo('all_customers');
}
};
var customer_promise = self_controller.store.find('customer', model.id);
customer_promise.then(onSuccess).catch(onError);
But I am unable to catch HTTP errors (401 unauthorized, 500 Internal Server Error, etc.).
ember-data provides a hook ajaxError to handle all error msgs in DS.RestAdapter. If you write it for ApplicationAdapter it will be used by all models based on RestAdapter. Here is how code looks(extracted from ember docs)
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
return new DS.InvalidError(jsonErrors);
} else {
return error;
}
}
});
Link for same in ember docs http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_ajaxError
I'm new to ember, and this is probably something very simple I'm missing. The "422 (Unprocessable Entity)" are something I'm setting up on the server side, and not part of a library, so if it needs to be something different for a 422, I couldn't find the docs.
Browser is chrome. Server side is Hunchentoot.
This is the error shown on the chrome console:
POST http://localhost:8080/users 422 (Unprocessable Entity) jquery.js:8706
send jquery.js:8706
x.extend.ajax jquery.js:8136
(anonymous function) ember-data-1.0.0-beta.8.js:2016
invokeResolver ember-1.5.1.js:9646
Promise ember-1.5.1.js:9632
Adapter.extend.ajax ember-data-1.0.0-beta.8.js:2005
Adapter.extend.createRecord ember-data-1.0.0-beta.8.js:1794
_commit ember-data-1.0.0-beta.8.js:11343
(anonymous function) ember-data-1.0.0-beta.8.js:10516
Ember.EnumerableUtils.forEach ember-1.5.1.js:1932
Ember.Object.extend.flushPendingSave ember-data-1.0.0-beta.8.js:10501
DeferredActionQueues.flush ember-1.5.1.js:6127
Backburner.end ember-1.5.1.js:6215
Backburner.run ember-1.5.1.js:6254
Ember.run ember-1.5.1.js:6664
handleRegisteredAction ember-1.5.1.js:38510
(anonymous function) ember-1.5.1.js:22259
x.event.dispatch jquery.js:5095
v.handle jquery.js:4766
This is the json that's being returned:
{"errors" : { "username" : ["Username exists"]}}
The adapter:
App.ApplicationAdapter = DS.RESTAdapter
Here's the model, the alert is never called:
App.User = DS.Model.extend({
username: DS.attr('string'),
password: DS.attr('string'),
becameInvalid: function( model) {
alert('Whoops')
}
})
The signup controller:
App.SignupController = Ember.ObjectController.extend({
confirm_password: "",
actions: {
signup: function() {
var user = this.get('model')
var self = this
user.save().then( function( post) {
self.transitionToRoute('/tasks', post)
})
}
}
})
The router:
App.SignupRoute = Ember.Route.extend({
model: function() {
return this.store.createRecord('user')
}
})
I had to add this to the adapter in my code:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
return new DS.InvalidError(jsonErrors);
} else {
return error;
}
}
});
Very frustrating
My server uses sensorID as the primary key on my Sensor model. I've tried the following
App.SensorSerializer = DS.RESTSerializer.extend({
primaryKey: "sensorID"
});
based on what I see in this test, but it's not working. I'm getting an error:
Error while loading route: Error: No model was found for '0'
I'm using a custom adapter. The response is JSONP:
jQuery203041337518650107086_1397489458691([{"sensorID":1,"address":"XXX, YYY","latitude":"nnnn","longitude":"mmmm"...
but when I inspect the data that gets returned, it's a normal array:
// App.SensorAdapter
findAll: function(store, type, sinceToken) {
var url = 'http://blahblahblah/?callback=?';
var query = { since: sinceToken };
return new Ember.RSVP.Promise(function(resolve, reject) {
jQuery.getJSON(url, query).then(function(data) {
debugger;
// data.forEach(function(s) {
// s.id = +s.sensorID;
// });
Ember.run(null, resolve, data);
}, function(jqXHR) {
jqXHR.then = null; // tame jQuery's ill mannered promises
Ember.run(null, reject, jqXHR);
});
});
What is the correct syntax for Ember Data 1.0.0-beta.7?
Try this:
App.Adapter.map('App.Sensor', {
primaryKey: 'sensorID'
});
Is there any way to handle 500 status error, when creating a new model e.g
var model = this.store.createRecord('user');
model.save().then(function() {
}, function(error) {
// this callback will not be executed if 500 status response
});
However, I can catch it using becameError event on model, but in this case an error object will not be available.
Thanks for your answers.
It looks like a 500 makes it back to the reject route
http://emberjs.jsbin.com/OxIDiVU/159/edit
You can override DS.Model save and assign values from the error hash to your models
App.Model = DS.Model.extend({
save: function() {
var _this = this;
return this._super().then(function(obj) {
return new Ember.RSVP.Promise(function(resolve) {
return resolve(obj);
});
}, function(error) {
//Do something with error here
_this.set('error_status', error.status)
return new Ember.RSVP.Promise(function(resolve, reject) {
return reject(error);
});
});
}
});
Note. becameError event called before error function in save method, so 'error_status' isn't set when becameError called.
var model = this.store.createRecord('user');
model.save().catch(function(error) {
error.status; // status code
});
how is possible handle restAdapter errors from store or adapter?
Now I am using this code:
App.ApplicationRoute = Ember.Route.extend({
model: function(){
var self = this;
return this.store.find('item').then(function(data){
return data;
}, function (error){
console.log('error');
return [];
});
},
});
It would be better something more general. Thanks
Until there is some more sophisticated error handling throughout ember data, you could do something like the following to handle network errors in a cross-cutting way:
Extend the RESTAdapter to parse errors from the xhr object
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
jqXHR = this._super(jqXHR) || {status : 'unknown'};
var error;
if (jqXHR.status === 404) {
error = 'not_found';
} else if (...) {
...
} else {
error = 'dunno';
}
return error;
}
});
Extend the store to publish error events when bad things happen
App.Store = DS.Store.extend(Ember.Evented, {
recordWasError: function (record, reason) {
this._super.apply(this, arguments);
this.trigger('error', reason);
}
});
Catch the error in your Application Route
App.ApplicationRoute = Ember.Route.extend({
setupController: function () {
this.get('store').on('error', function (error) {
// Do something with the error
console.error(error);
});
},
...
});