Bad state in delete action with Ember Data - ember.js

I'm trying to put together what I think is a pretty straightforward ember delete action (based on this example: http://discuss.emberjs.com/t/migrating-from-ember-data-0-13-to-1-0-0-beta-1-my-findings/2368) from an Index Controller and I think I must be missing something.
actions: {
deleteZone: function (zone) {
if (confirm("Are you sure you want to delete the zone?")) {
var _this = this;
zone.deleteRecord();
zone.save().then(
function () {
_this.transitionToRoute('zones.index');
},
function (error) {
zone.rollback();
}
);
}
}
}
I'm running into trouble when I try to delete a zone that has a corresponding dependency. In this case, the server (Rails 4) throws an exception and returns the following JSON:
{"status":422,"message":"Cannot delete record because of dependent projects","errors":{}}
However, while I believe the server returns the correct error, the UI seems to fail before it gets that far. If I put a debugger on the line after zone.rollback() inside the catch function I get this error:
Attempted to handle event `becameInvalid` on <App.Zone:ember1276:6> while in state root.deleted.inFlight. Called with {}.
I'm running on ember 1.4.0-beta.1, ember-data 1.0.0-beta.4 (ActiveModelAdapter) and rails 4.0.1. Any suggestions would be much appreciated, thanks!

Manually transitioning to a loaded.saved state after the rollback seems to resolve the issue:
zone.transitionTo('loaded.saved');
After upgrading to the latest ember/ember-data and slightly modifying the JSON response I'm now able to extract the error message out of the JSON using the error reference passed in to the catch expression.
{"status":422,"message":"translation missing: en.Invalid zone","errors":{"base":["Cannot delete record because dependent projects exist"]}}
And ember versions:
DEBUG: Ember : 1.4.0-beta.1+canary.4d69bca7 ember.js?body=1:3307
DEBUG: Ember Data : 1.0.0-beta.5+canary.2e773365 ember.js?body=1:3307
DEBUG: Handlebars : 1.0.0 ember.js?body=1:3307
DEBUG: jQuery : 1.10.2

I ran into to this issue as well. Running model.transitionTo('loaded.saved'); helped to vaoid any errors thrown, but the model is destroyed as well.
If one wants to keep the model in the store, one must re-inject it, which seems odd, but works:
var model = this.get('model');
var store = model.store;
model.deleteRecord();
model.save().catch(function(err){
model.transitionTo('loaded.saved');
var payload = model.serialize({includeId: true});
store.unloadRecord(model)
store.pushPayload('nestedSet',{nested_set:payload});
});
I am running:
DEBUG: -------------------------------
DEBUG: Ember : 1.8.0-beta.1+canary.d6d4f01d
DEBUG: Ember Data : 1.0.0-beta.9
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery : 1.11.1
DEBUG: -------------------------------
For version testing completeness same happens on ember-data#1.0.0-beta.7!
Hope it helps, if anyone runs into this too.

Related

Ember Super Rentals Tutorial 3.15 - Working with data

I was following the ember Super Rental 3.15 tutorial, when I got to the working with data section, I updated the route index file with model hooks, the page stopped working. Also I am finding ember tutorials to be incomplete.
error says property of map is undefined
code in routes index.js file:
import Route from '#ember/routing/route';
const COMMUNITY_CATEGORIES = [
'Condo',
'Townhouse',
'Apartment'
];
export default class IndexRoute extends Route {
async model() {
let response = await fetch('/api/rentals.json');
let { data } = await response.json();
return data.map(model => {
let { attributes } = model;
let type;
if (COMMUNITY_CATEGORIES.includes(attributes.category)) {
type = 'Community';
} else {
type = 'Standalone';
}
return { type, ...attributes };
});
}
}
image if error message:
Your problem is that fetch('/api/rentals.json'); does not return the correct data. And so when you do let { data } = await response.json(); then data will be undefined and you can not do undefined.map.
So the code you posted is correct. The problem is somewhere else. You can check:
did you correctly add the rentals.json file? If you open http://localhost:4200/api/rentals.json do you see the data? So have you done this?
I see some error from mirage. The super-rentals tutorial does not use mirage. I can see this here (sidenote: that git repo is automatically created from the guides, so its always up to date). So this could be your problem. Depending how you configure mirage it will basically mock all your ajax requests. This means that fetch(... will no longer work then expected, mirage assumes you always want to use mocked data and you did not configure mirage correctly. You can try to remove mirage from your package.json, rerun npm install, restart the ember server and try it again.

This.get(‘model’).addObjects(records); throw exception “Uncaught TypeError: internalModel.getRecord is not a function”

Friends I need help, after updating to Ember 2.X, my infinite scroll stopped working. On reaching page end, I query store to get new records:
load_more: function(){
var self = this;
this.get("store").query("actor",{pg: 1}}).then(function(records) {
self.get('model').addObjects(records); // this throw excepton
}
}
This was working perfectly but now self.get('model').addObjects(records); throw exception "Uncaught TypeError: internalModel.getRecord is not a function" in record-array.js at line 86 "return internalModel && internalModel.getRecord()".
I tried using self.get('model').pushObjects(records); but it also give same error, please help
We had the same issue.. try doing
self.get('model').toArray().pushObjects(records)
I assume this is on your controller. You could try using Ember get, which better used when fetching items on promises:
load_more: function(){
Ember.get(this, "store").query("actor",{ pg: 1 }}).then((records) => {
Ember.get(this, 'model').addObjects(records);
}
}
Ember 2.x should give you access to the latest version of babel, which means you can use => functions (and don't have to do var self = this as it will do it for you).

Reload model/update template on createRecord save

I see this question is being ask all over again still don't find solution that works for such a trivial task.
This url displays a list of navigations tabs for workspaces.
http://localhost:4200/users/1/workspaces
Each of tab resolves to
http://localhost:4200/users/1/workspaces/:wid
Also on the I have a button that suppose to create a new workspace as well as new tab.
Here how controller for looks:
export default Ember.Controller.extend({
actions: {
newWorkspace: function () {
this.get('currentModel').reload();
var self = this;
var onFail = function() {
// deal with the failure here
};
var onSuccess = function(workspace) {
self.transitionToRoute('dashboard.workspaces.workspace', workspace.id);
};
this.store.createRecord('workspace', {
title: 'Rails is Omakase'
}).save().then(onSuccess, onFail);
}
}
});
When I click on button I see in ember inspector new record indeed created as well as url redirected to id that represents newly created workspace.
My question is how to force model/template to reload. I have already killed 5h trying model.reload() etc. Everything seem not supported no longer. Please please help.
UPDATE
When adding onSuccess
model.pushObject(post);
throws Uncaught TypeError: internalModel.getRecord is not a function
I believe you should call this.store.find('workspace', workspace.id) for Ember Data 1.12.x or earlier. For 1.13 and 2.0 there are more complicated hooks that determine whether or not the browser should query the server again or use a cached value; in that case, call this.store.findRecord('workspace', workspace.id, { reload: true }).
I do not know if this help. I had a similar problem. My action was performed in the route. Refresh function took care of everything.

TypeError: Cannot read property 'forEach' of undefined Ember-Data

When I hit '/' of my app, I am getting the stack trace below
Error while loading route: TypeError: Cannot read property 'forEach' of undefined
at Function.Model.reopenClass.eachAttribute (http://localhost:3000/assets/ember-data.js?body=1:4870:32)
at JSONSerializer.extend.normalizeAttributes (http://localhost:3000/assets/ember-data.js?body=1:2906:16)
at JSONSerializer.extend.normalize (http://localhost:3000/assets/ember-data.js?body=1:2827:14)
at superWrapper (http://localhost:3000/assets/ember.js?body=1:1293:16)
at superFunction [as _super] (http://localhost:3000/assets/ember.js?body=1:7724:16)
at RESTSerializer.extend.normalize (http://localhost:3000/assets/ember-data.js?body=1:378:21)
at superWrapper [as normalize] (http://localhost:3000/assets/ember.js?body=1:1293:16)
at null.<anonymous> (http://localhost:3000/assets/ember-data.js?body=1:3179:35)
at Array.map (native)
at JSONSerializer.extend.extractArray (http://localhost:3000/assets/ember-data.js?body=1:3178:37)
Relevant Code is Here (in Coffeescript):
Plnnr.ApplicationRoute = Ember.Route.extend(
model: ->
#store.find('stage')
)
Plnnr.Stage = DS.Model.extend(
tasks: DS.hasMany("task")
name: DS.attr("string")
description: DS.attr("string")
position: DS.attr("number")
)
Plnnr.ApplicationAdapter = DS.ActiveModelAdapter.extend(
namespace: 'v1'
)
The API is setup with Rails Serializer and setting a breakpoint shows that the Adapter is successfully retrieving the data.
I also set a breakpoint in Ember-data.js, at the origin of where the failure starts (when .normalize is called in the code below):
var normalizedArray = map.call(payload[prop], function(hash) {
return typeSerializer.normalize(type, hash, prop);
}, this);
At that time, type = DS.Model and hash = the serialized API payload.
I'm new to Ember and arent familiar with how to interpret the documentation. Does anyone know what could be wrong, and have any suggestions on how I can trace the problem?
Thanks!
It turns out the problem was because I was using Coffeescript with Ember.
I declared my ember Task class like so:
class Plnnr.Task extends DS.Model
instead of
Plnnr.Task = DS.Model.extend
These two are NOT equivalent. While Stage correctly used the second convention, Task (which belongs to Stage) incorrectly used the first convention. When the JSON came back with both Stage and Task objects, the serializer threw up the trace above, because it couldn't handle Plnnr.Task parsing correctly

How should errors be handled when using the Ember.js Data RESTAdapter?

ember-data.js: https://github.com/emberjs/data/tree/0396411e39df96c8506de3182c81414c1d0eb981
In short, when there is an error, I want to display error messages in the view, and then the user can 1) cancel, which will rollback the transaction 2) correct the input errors and successfully commit the transaction, passing the validations on the server.
Below is a code snippet from the source. It doesn't include an error callback.
updateRecord: function(store, type, record) {
var id = get(record, 'id');
var root = this.rootForType(type);
var data = {};
data[root] = this.toJSON(record);
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
this.didUpdateRecord(store, type, record, json);
}
});
},
Overall, what is the flow of receiving an error from the server and updating the view? It seems that an error callback should put the model in an isError state, and then the view can display the appropriate messages. Also, the transaction should stay dirty. That way, the transaction can use rollback.
It seems that using store.recordWasInvalid is going in the right direction, though.
This weekend I was trying to figure the same thing out. Going off what Luke said, I took a closer look at the ember-data source for the latest commit (Dec 11).
TLDR; to handle ember-data update/create errors, simply define becameError() and becameInvalid(errors) on your DS.Model instance. The cascade triggered by the RESTadapter's AJAX error callback will eventually call these functions you define.
Example:
App.Post = DS.Model.extend
title: DS.attr "string"
body: DS.attr "string"
becameError: ->
# handle error case here
alert 'there was an error!'
becameInvalid: (errors) ->
# record was invalid
alert "Record was invalid because: #{errors}"
Here's the full walk through the source:
In the REST adapter, the AJAX callback error function is given here:
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
Ember.run(this, function(){
this.didUpdateRecord(store, type, record, json);
});
},
error: function(xhr) {
this.didError(store, type, record, xhr);
}
});
didError is defined here and it in turn calls the store's recordWasInvalid or recordWasError depending on the response:
didError: function(store, type, record, xhr) {
if (xhr.status === 422) {
var data = JSON.parse(xhr.responseText);
store.recordWasInvalid(record, data['errors']);
} else {
store.recordWasError(record);
}
},
In turn, store.recordWasInvalid and store.recordWasError (defined here) call the record (a DS.Model)'s handlers. In the invalid case, it passes along error messages from the adapter as an argument.
recordWasInvalid: function(record, errors) {
record.adapterDidInvalidate(errors);
},
recordWasError: function(record) {
record.adapterDidError();
},
DS.Model.adapterDidInvalidate and adapterDidError (defined here) simply send('becameInvalid', errors) or send('becameError') which finally leads us to the handlers here:
didLoad: Ember.K,
didUpdate: Ember.K,
didCreate: Ember.K,
didDelete: Ember.K,
becameInvalid: Ember.K,
becameError: Ember.K,
(Ember.K is just a dummy function for returning this. See here)
So, the conclusion is, you simply need to define functions for becameInvalid and becameError on your model to handle these cases.
Hope this helps someone else; the docs certainly don't reflect this right now.
DS.RESTAdapter just got a bit more error handling in this commit but we are still not yet at a point where we have a great recommendation for error handling.
If you are ambitious/crazy enough to put apps in production today with ember-data (as I have been!), it is best to make sure that the likelihood of failures in your API is extremely low. i.e. validate your data client-side.
Hopefully, we can update this question with a much better answer in the coming months.
I just ran into such a situation, not sure if this is already explained anywhere.
I am using:
Em.VERSION : 1.0.0
DS.VERSION : "1.0.0-beta.6"
Ember Validations (dockyard) : Version: 1.0.0.beta.1
Ember I18n
The model was initially mixedin with Validation mixin.
App.Order = DS.Model.extend(Ember.Validations.Mixin, {
.....
someAttribute : DS.attr('string'),
/* Client side input validation with ember-validations */
validations : {
someAttribute : {
presence : {
message : Ember.I18n.t('translations.someAttributeInputError')
}
}
}
});
In the template, corresponding handlebars is added. (note that ember validations will automatically add errors to model.errors.<attribute> in case of input validations, I will be using same trade-off in server validations as well)
<p>{{t 'translations.myString'}}<br>
{{view Ember.TextField valueBinding="attributeName"}}
{{#if model.errors.attributeName.length}}<small class="error">{{model.errors.attributeName}}</small>{{/if}}
</p
Now, we will be saving the Order
App.get('order').save().then(function () {
//move to next state?
}, function(xhr){
var errors = xhr.responseJSON.errors;
for(var error in errors){ //this loop is for I18n
errors[error] = Ember.I18n.t(errors[error]);
}
controller.get('model').set('errors', errors); //this will overwrite current errors if any
});
Now if there is some validation error thrown from server, the returned packet being used is
{"errors":{"attributeName1":"translations.attributeNameEror",
"another":"translations.anotherError"}}
status : 422
It is important to use status 422
So this way, your attribute(s) can be validated client side and again on server side.
Disclaimer : I am not sure if this is the best way!
Since there's currently no good solution in stock Ember-Data, I made my own solution by adding an apiErrors property to DS.Model and then in my RestAdapter subclass (I already needed my own) I added error callbacks to the Ajax calls for createRecord and updateRecord that save the errors and put the model in the "invalid" state, which is supposed to mean client-side or server-side validations failed.
Here's the code snippets:
This can go in application.js or some other top-level file:
DS.Model.reopen({
// Added for better error handling on create/update
apiErrors: null
});
This goes in the error callbacks for createRecord and updateRecord in a RestAdapter subclass:
error: function(xhr, textStatus, err) {
console.log(xhr.responseText);
errors = null;
try {
errors = JSON.parse(xhr.responseText).errors;
} catch(e){} //ignore parse error
if(errors) {
record.set('apiErrors',errors);
}
record.send('becameInvalid');
}