This is very similar to this question Force reload of dirty/invalid model in Ember
I'm using ember.js with ember-data.
DEBUG: ------------------------------- ember.js?compile=false:3521
DEBUG: Ember : 1.5.0 ember.js?compile=false:3521
DEBUG: Ember Data : 1.0.0-beta.7+canary.e3b896bc ember.js?compile=false:3521
DEBUG: Handlebars : 1.3.0 ember.js?compile=false:3521
DEBUG: jQuery : 1.11.0 ember.js?compile=false:3521
DEBUG: -------------------------------
The server returns a 422 with errors response for validation errors, which gets added to model's errors and marked as invalid, then the error shows up on my template. This all works fine. However,
after the model is marked invalid following an attempted save(), if I then linkTo another route let's say to /show/id to view that same model. The data store retrieves the invalid model with invalid values instead of getting a fresh valid model. I have tried doing a rollback() onFail like: group.save().then(onSuccess, onFail); and the rollback works, however it also clears the model errors and refreshes the template so the user never sees the validation errors. What I want is to show the validation errors and if a linkTo another route happens; From there the model with invalid state, should no longer be pulled from the data store, but rather pulled from the server again. The only way I can get a valid model currently is to reload the entire page.
I have also tried forcing a reload using the model hook in the router, but this seems to cause errors:
Ricauth.GroupShowRoute = Ember.Route.extend({
model: function(params) {
var group = this.store.find('group', params.id);
group.reload(); // doesn't work, causes error
return group;
},
setupController: function(controller, group) {
controller.set('model', group);
controller.set('readOnly', true);
controller.set('meta', Ember.copy(this.store.metadataFor("group")))
}
});
This is not really a good way to do it anyway, since I'm essentially reloading the model every time ShowRoute is requested. I also tried to check group.isValid, however it's undefined at that point. Any ideas on how to get this reloaded and only when the model is invalid?
I found a reasonable solution to this using onFail and unloadRecord(). unloadRecord will remove the record from the datastore so the store will then retrieve from the server next time this record is queried. My update action
actions: {
update: function (group) {
var self = this;
var onSuccess = function(group) {
console.info("save: "+group);
self.transitionTo('group.show', group);
};
var onFail = function(error) {
console.info("failed: "+error.message);
group.unloadRecord();
};
if(group.get('currentState').stateName == 'root.loaded.updated.uncommitted') {
group.save().then(onSuccess, onFail);
}
else {
onSuccess(group);
}
}
}
So while the unload does remove the record from the datastore, it goes into a state 'root.deleted.saved'. From this state I can't seem to then save the record because of the way the ember data state manager works. At this point I'm just having a difficult time understanding why I can't make something so conceptually simple work. Does anyone else have enough experience with ember-data to know how this should be handled?
Use the deactivate method on Ember.Route (http://emberjs.com/api/classes/Ember.Route.html#method_deactivate). There you can check to see if the model is invalid, then rollback the record.
deactivate: function(){
if(!this.model().get("isValid")){
this.model().rollback();
}
}
Related
When my route model uses this.store.find('user') it updates automatically and the template shows new records created using createRecord. So the following works as expected:
Route:
model: function(){
return this.store.find('user');
}
Template:
{{#each user in model}}
{{user.username}}<br>
{{/each}}
Controller Code:
// Triggered when form submitted to create a new user
var newUser = this.store.createRecord('user', {
username: this.get('username').trim()
});
// new user shows up in template immediately after createRecord
If I change my route model to use the query params version of find the template no longer updates when I do a createRecord
Route:
model: function(){
// query params version of store.find
return this.store.find('user', {enabledOnly: false, limit: 100});
}
Controller Code:
// Triggered when form submitted to create a new user
var newUser = this.store.createRecord('user', {
username: this.get('username').trim()
});
// new user does not show up in template at all
It seems like this might be a bug as the only change in the code is switching from the basic find('user') to the version that has query params. Is this expected behavior for ember data? Why would the model not update the template after createRecord is called when the query param version of find is used (i.e., find('user', {}))
I was able to create a jsbin that demonstrates the issue.
http://jsbin.com/kilaridoso/2/edit?html,js,output
Thank you!
I am using the following version:
DEBUG: -------------------------------
ember.debug.js:5197DEBUG: Ember : 1.11.1
ember.debug.js:5197DEBUG: Ember Data : 1.0.0-beta.16.1
ember.debug.js:5197DEBUG: jQuery : 1.11.3
ember.debug.js:5197DEBUG: Ember Simple Auth : 0.8.0-beta.2
ember.debug.js:5197DEBUG: -------------------------------
Thinking this was a bug I posted over on Ember-Data's GitHUB page. The answer provided is that this is expected behavior. Here is the full response from wecc (thanks!)
Using store.find(type, query) does not return a live RecordArray so
the behavior you're describing is correct.
You should be able to use store.filter(type, query, filter) (docs)
instead.
The reason for store.find(type, query) not updating with the newly
created record is that query is just sent to the server, there's no
way for ED to know if new records "match" that query or not. Sometimes
the query might be a simple filter like { isUnread: true } but it can
also be something like { since: '2015-05-10 10:51' }. Using
store.filter(type, query, filter) on the other hand, passing both
query and a filter callback function, ED can pass query to the server
and use the filter function on all new records to know if it's to be
included in the live RecordArray or not.
Here is a link to the answer:
https://github.com/emberjs/data/issues/3057
I recently upgraded from ember data 1.0.0-beta.2 to ember data 1.0.0-beta.9. There is a piece of code to delete a record that works perfectly fine in beta 2, but it doesn't work in beta.9
My model looks like this:
AS.Question = DS.Model.extend({
questionName: DS.attr('string'),
childQuestions: DS.hasMany('question', {
async: true
})
});
And my delete method looks like this:
deleteQuestion: function (question) {
var self = this;
question.deleteRecord();
question.save().then(function () {
console.log('success');
//unload child records from the store because server removes all the child questions
}, function (failureResponse) {
console.log(failureResponse);
console.log('failure');
//perform rollback
})['finally'](function () {
console.log('in finally');
});
}
In ember data beta 9, it never goes through the success function if my question has child questions, it always goes to the second function that catches failure. The delete only works if a question does not have child questions. I also tried deleting all child questions first before deleting a parent question model but still it goes directly to failure block.
The delete request however resolves correctly with http status 200 and the response from the delete request is an empty object {}.
The "failureResponse" it coughs out is :
Error: Assertion Failed: calling set on destroyed object
at new Error (native)
at Error.EmberError (lib/ember/ember.js:13538:33)
at Object.Ember.assert (lib/ember/ember.js:3722:27)
at set (lib/ember/ember.js:16834:23)
at Ember.Object.extend.hasManyDidChange (lib/ember/ember-data.js:7112:11)
at null.<anonymous> (lib/ember/ember-data.js:7101:18)
at lib/ember/ember.js:14897:34
at Object.OrderedSet.forEach (lib/ember/ember.js:14739:28)
at Object.Map.forEach (lib/ember/ember.js:14895:26)
at Ember.Object.extend.reloadHasManys (lib/ember/ember-data.js:7098:23)
It must have been caused by :
hasManyDidChange: function(key) {
var hasMany = this._relationships[key];
if (hasMany) {
var records = this._data[key] || [];
set(hasMany, 'content', Ember.A(records));//ONE OF THESE SETS IN EMBER-DATA
set(hasMany, 'isLoaded', true);
hasMany.trigger('didLoad');
}
},
Any idea how can I make my delete work in this beta release? Also if you know how to quickly create a fake server then I could probably create a jsbin for it - I tried using sinon but couldn't make things work.
Regards,
Dee
UPDATE:
This issue must be caused by something else, since I couldn't recreate this issue here : http://jsbin.com/yofuqa/2/
But still its weird that the same logic would work in beta 2 but not in beta 9!
With beta .11, the hasMany relation do not even load. Here is the jsbin: http://jsbin.com/yofuqa/3/
I'm currently using EmberJs along with Ember-Data to build an app backed by a Laravel JSON api.
I got a little issue on the saving process, mostly on model creation.
Here is my workflow :
The Ember ObjectController saves itself this.get("model").save()
Laravel (REST api) receives the data and persist it, therefore
creating a unique ID for it
The api return the new data (that
respect Ember convention) with the proper ID
???? Ember-Data doesn't
seems to care about the response since it does nothing...
The problem here : the id remains undefined even if it has been given...
The workaround I found is to reload models... but it's a big performance flaw considering that the data I want to be reloaded it available to Ember straight after the save()
any ideas ?
EDIT **
The problem only occurs on the first object that I add. When I repeat the process the next objects are refreshed correctly. If I hit refresh, it start again : the first object miss his refresh and the following are okay.
Here my code about the add process :
route
App.CategoriesNewRoute = Ember.Route.extend({
model: function()
{
return this.store.createRecord("category").set("active", true);
},
setupController: function(ctrl, model)
{
ctrl.set("errors", 0);
ctrl.set("model", model);
}
});
I don't have any Router for CategoriesRoute since the data is all in my ArrayController from the start.
controller
App.CategoriesNewController = Ember.ObjectController.extend({
needs: "application",
actions:
{
save: function()
{
this.get("model").save();
this.get("target").transitionTo("categories");
},
cancel: function()
{
this.get("model").rollback();
this.get("target").transitionTo("categories");
}
}
});
EDIT ** 2
I tried the code provided below with no success...
I added 2 records, and the first don't have it's ID... the second got it, so the problem appears to only be on the first save...
Here are the 2 responses I got from my API
ObjectA
{"category":{"nameFr":"aaa","nameEn":"aaa","active":true,"id":10}}
ObjectB
{"category":{"nameFr":"bbb","nameEn":"bbb","active":true,"id":11}}
It could be because you're transitioning before the save finishes, and so the model hook on the categories route fires while the model you're saving is still in flight (are you getting any errors in the console?). Try changing the save action to
save: function()
{
var that = this;
this.get("model").save().then(function(){
that.get("target").transitionTo("categories");
});
},
Also, you don't need to this.get('target')... as there's a controller method transitionToRoute. You can simplify to:
save: function()
{
var that = this;
this.get("model").save().then(function(){
that.transitionToRoute("categories");
});
},
Found that the problem seems to be on Ember-Data's side...
documented the whole thing here :
http://discuss.emberjs.com/t/missing-id-on-first-save-on-a-new-object/4752
I am migrating from EMber data 0.13 to 1.0.0 beta. According to the documentation (https://github.com/emberjs/data/blob/master/TRANSITION.md), the following should work:
App.AuthorsNewRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('author');
},
actions: {
save: function() {
this.modelFor('author').save();
}
}
})
However, in my case, I always get a "Cannot call method 'save' of undefined" error".
When using "this.get('currentModel').save();", it works when using the save action in the route. When putting the save action in the controller, it no longer works. Same error: Cannot call method 'save' of undefined" error.
How can I access the newly created record in the controller and save it ?
Can somebody provide a simple example ?
thx
Marc
You right to use this in the route :
this.get('currentModel').save();
In the controller you should use :
this.get('model').save();
I'm having an issue with Ember-Data transactions.
I have a DS.Model like so
App.MyModel = DS.Model.extend({
id: DS.attr(),
answers: DS.hasMany('App.Answer') // another model
});
Then it is initiated later like so, in the a Route
model: function(){
var transaction = this.get('store').transaction();
return transaction.createRecord(App.MyModel, {
id: '1'
});
}
I have a Model that makes a request to my back end server using transaction and commit.
this.get('content.transaction').commit();
With the intent that answers is updated on the server side and sent back to me.
If the content hasn't been updated yet, I call this
this.get('content').reload();
And the request is sent again.
This all works fine. answers gets populated if the id is found.
My issue is that occasionally, depending on what I get back from the server, I have to make another server request. The initial request works fine with
this.get('content.transaction').commit();
but when I try to reload the transaction, I get an error, as follows
Uncaught Error: Attempted to handle event `loadedData` on <App.Answer> while in state rootState.loaded.updated.uncommitted. Called with undefined
Now when I remove the reload, I no longer get the error, also when I check the console of Chrome under the network tab, I can see that the results I want are being sent back but they are not being updated in my DS Model. answers is left undefined.
Anyone know why this is happening? Am I using the transactions wrong?
EDIT
Application.SearchController = Ember.ObjectController.extend({
isComplete: function () {
return this.get('content.answers.length') !== 0;
},
search: function () {
this.get('content.transaction').commit();
var record = this.get('content');
var interval = setInterval(function (controller) {
if (controller.get('isComplete')) {
controller.transitionToRoute("search.view");
clearInterval(interval);
} else {
record.reload();
}
}, 5000, this);
}
});
SO basically some work in done in my route to set up my models and set them to the content, the model has an id that will be used on the server side and sent back with the results of the search then added to "answers".
This work fine until there are multiple results are found. Then a new model is created and the search function is called again on a different controller, with different content. This time round on the line
record.reload();
I get the error
Uncaught Error: Attempted to handle event loadedData on while in state rootState.loaded.updated.uncommitted. Called with undefined
So the server still responds with the correct results but the "answers" is not updated on the client side.
After the first commit the transaction is placed on the default transaction.
Error Attempted to handle event `loadedData` : Object not updated after deleteRecord
And remember always setup the router first.
Your MyModel record is locally modified (client side). Calling reload will try to update it, which is prohibited in the current state of the record.
You can check this with a command:
console.log( this.get('content.stateManager.currentState.path') );
this.get('content').reload();
This should display in your console that the record is in the uncommitted state.
UPDATE:
You can't use a timer. Everything is asynchronous and you have no guarantee that your model will be updated during that interval. Which means that while you commit your record, you may reload it at the same time (this would generate the error you see).
What you want is something like that:
Application.SearchController = Ember.ObjectController.extend({
search: function () {
var record = this.get('content'),
controller = this;
record.one('didCommit', function() {
controller.transitionToRoute("search.view");
});
record.transaction.commit();
}
});