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();
}
});
Related
I thought I understood how the store.findAll and the Promise.All works. But I have run into some strange behavior.
I have two findAll(), but only one of them is fullfilled after the Promise.All gets into the then()-part.
See this code
export default Route.extend({
model() {
var self = this;
return Ember.RSVP.Promise.all([
self.store.findAll('contact'),
self.store.findAll('message')
]).then(function(values) {
var contacts = values[0];
var messages = values[1];
var numberOfContacts = contacts.get('length'); // This is 39 as expected.
var numberOfMessages = messages.get('length'); // This is 0. Expected is 1.
...
There must be something different with messages and contacts, but I cannot figure out what it is. They have very similar models and they have very similar backend API handling. From the network traffic I can see that a message object is returned and if I call (later in the code, after the model hook):
store.peekAll('message');
I get the message object I expect.
I use ember 3.0.0
I figured it out. It is due to a strange behavior of findAll() in Ember.
FindAll() will return immediately with the elements that was already present in the store. Later, when more objects have been retrieved from the server, the store is updated, but the promise of the findAll()-call is long gone.
To work around this strange behavior, there is an option to the findAll() method.
{reload: true}
It is used this way:
return self.store.findAll('message', { reload: true }).then(messages => {
var messageLength = messages.get('length');
...
With this reload-option set, findAll() and promises work as expected.
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/
here is the issue i am trying to resolve.
I am trying to retrieve contact record from store via query string params (fields: id and tab). i can see the contact model being retrieved in the browser console .
since find operation is being executed on query params the response is array of one contact record.
that's why in the controller code beneath i am extracting the contact model using contact.get('firstObject').
however nothing gets rendered in the browser as before this whole operation is done the template gets rendered.
I dont understand this behavior. since i am wrapping this operation in RSVP promise call.
till this promise is returned from this model hook, Ember.js should block until the promise is resolved . please let me know what is going wrong here.
export default Ember.ObjectController.extend({
model:function(){
var self = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
self.store.find('contact',{id:1, tab: "contactInfo"}).then(function(contact) {
contact.get('firstObject');
});
});
}.property('model')
});
You were almost there. Apart from some redundant code, you should of course return your firstObject. This should work:
model:function(){
return this.store.find('contact',{id:1, tab: "contactInfo"}).then(function(contact) {
return contact.get('firstObject');
});
Promises are ember's approach on handling asynchronous logic and the whole goal is not to block the whole application. When the data gets in, the template will be updated.
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();
}
}
UPDATE:
THIS IS A NON-ISSUE
(see below)
So I wrote a jsfiddle to show the bad behavior except the fiddle works! and my real code doesn't. The only difference is I am using the RESTAdapter in my real code so the data is pulled from server instead of FIXTURES.
In the jsfiddle: first click 'Simulate 1st manual load', then click the 2nd button to see it work properly (i.e. loading new or updated data to the store multiple times in a row)
http://jsfiddle.net/iceking1624/NZZ42/4/
The Issue
I am sending updated information over websockets to my Ember App. I successfully set up a listener to trigger a function on the correct controller and am able to update records the first time. But all successive attempts do not update the store and I wonder if this has to do with the state of the store? But I am unsure of how to handle if that is the case.
This is the code that updates or adds the records that come over websockets:
App.SessionController = Ember.ObjectController.extend({
updateReceived: function(data) {
console.log(data);
DS.defaultStore.load(App.Choice, data.choice);
DS.defaultStore.load(App.Question, data.question);
}
});
Notice the console.log(data) part. Every single time I send updated data via websockets, updateReceived is called and the correct data is logged every time, but DS.defaultStore.load(...) only works the first time.
The reason I update both App.Question & App.Choice is because they have a relationship:
App.Question = DS.Model.extend({
"choices" : DS.hasMany('App.Choice')
});
App.Choice = DS.Model.extend({
"question" : DS.belongsTo('App.Question')
});
I don't think the code below is relevant to the issue but just in case someone is interested, this is how I listen for events over websockets (using socket.io):
App.SessionRoute = Ember.Route.extend({
enter: function() {
this.socket = io.connect('http://10.0.1.4')
},
setupController: function(controller, model) {
var self = this;
this.socket.on('update', function(data) {
self.controller.send('updateReceived', data)
})
}
});
Are there any suggestions for how I can continuously load new or updated records directly into the store (and not just once)?
UPDATE:
The code is indeed correct. The new data was loading into the store just fine but I wasn't re-rending a view correctly when new/updated information was loaded into DS.defaultStore
I don't want to delete this question since others may find the information in it useful but vote how you like. I'm sorry I didn't catch this before writing the question.