While playing with ember I was trying to save both a parent and a child at the same time. What I noticed was that the child's parent_id was always being set to nil.
Then I saw this issue on GitHub. It seems the functionality was removed from Ember-Data but will be retured later. Until then we're ment to roll our own adapter to do this.
Reading #tomdale's response it doesn't seem that it would be that hard to implement, but I have a few implementation questions.
How can you tell when a records belongsTo association has changed? And what adapter hook would this go under?
Thanks!
The store keeps track of relationship changes with special objects. You can ask the store for the change object for a relationship like so:
store.relationshipChangeFor(child.get('clientId'), belongsToAssociationName);
If no change was made, it won't return anything.
But it's likely you won't ever need to do that. In the case of a relational data store, the child record will be responsible for persisting its belongsTo associations. On successful create/update, you can blindly mark all of those relationships as resolved and the store won't care if some of them weren't actually dirty.
In fact, that's exactly what DS.RESTAdapter does now:
DS.RESTAdapter = DS.Adapter.extend({
// ...
didSaveRecord: function(store, record, hash) {
record.eachAssociation(function(name, meta) {
if (meta.kind === 'belongsTo') {
store.didUpdateRelationship(record, name);
}
});
store.didSaveRecord(record, hash);
}
// ...
});
Whenever and wherever you decide you're ready to mark the relationship as resolved, simply call store.didUpdateRelationship(child, belongsToAssociationName).
All that being said, the real trick is not in marking the relationships as resolved (since that's already implemented)—it's in delaying the save of the child until after the parent has been created.
We've actually submitted a pull request that does just that. If it'll help you as much as it's helping us, we'd appreciate some support on the request.
Related
On my route im requesting via ember-data some records. Lets say the model-type is 'item'.
model: function(){
return this.get('store').find('item');
}
now ive got a component named 'my-foo' which should use the records to do something with the data. Therefore Im calling the component like that:
{{my-foo myItems=model}}
in my routes template. In the components js part, Im trying to get the myItems-field and iterate over them.
this.get('myItems').forEach(...);
Unfortunalety its not clear for me if the model i want to overgive to the component is an collection from records or just a single record (since on some routes the model is the result of store.find('item') on other store.find('item', 23424).
How can I check what kind of data arrives in the component.
(Also Im wondering what kind of object is it since im using ember-data. Is it a DS.recordarray or a promise or something else at this time?)
I can see two solutions to the problem:
Making component aware of the form that model receives
Checking and/or adjusting data type in component (in my opinion better default scenario)
As for making component aware - you could go with 2 approaches. Either differentiate in a way how your component take arguments, so there could be:
{{my-foo myItems=model}} - when you expect to receive multiple items
{{my-foo item=model}} - when you expect to receive single one
And then work accordingly further on, or - the second approach - is to actually split component (while extracting shared part to a different structure) so you would have my-foo for single items and my-foo-array for multiple.
Advantage of this approach is that you don't deal with what-if-multiple logic, that might grow to something unmanagable later on, yet usage of it is dependant on project requirements.
As for checking and/or adjusting - you already have data in, so could make assumption that your data is dirty and sanitize it using computed property. Below example, where single item is wrapped into an array.
export default Ember.Component.extend({
sanitizedItems: Ember.computed('items', function() {
var items = this.get('items');
if(!Array.isArray(items)) {
return [items];
} else {
return items;
}
})
});
Since you're using Ember.Data, depending on your setup, you might get a promise instead of object/array. In this case, you might want to resolve promise using this.get('items').then(function(items) { ... }) before doing sanitization, yet the idea behind is exactly the same.
You can check full example: Gist, Twiddle
I'm trying to update an app from Ember Data 1.0.0-beta.9 to 1.0.0-beta.11, and quite a bit seems to have changed. Specifically, I'm running into issues with finding out if a model instance does indeed have an associated model instance.
A = DS.Model.extend({
b: belongsTo('b', { async: true }),
});
B = DS.Model.extend({
a: belongsTo('a', { async: true }),
});
In Ember Data 1.0.0-beta.9, a.get('b') would simply return null if no associated model is found. That makes it easy to filter by computed property macros.
In Ember Data 1.0.0-beta.11, a.get('b') returns a promise, which makes it much harder to use in computed property macros. If the promise is fulfilled and the content of the promise is null, there's no associated record. But I have no idea whether it is possible to implement this check inside a Ember.computed.filter.
I have quite a few Ember.computed.filters probing quite a few Ember.isEmpty(a.get('b'))s, so I'm looking for a good way to check whether an object's async relationship is empty. Am I missing something obvious, like a built-in Ember Data api call? How would you implement such a check, if you need to filter by associated property presence/absence?
Well, to answer my own question, I got around this by filtering for Ember.isEmpty(a.get('b.id')), b/c I usually deal with persisted records and was able to ship around the edge cases. Sometimes it's so simple… :D
if the related record is just created and isNew is true, checking the id property would also return null. In my case this causes unexpected results. How I managed to overcome this is by checking the content property instead. For a newly created record this will return the model class, and null if the related object is empty.
Is there a standard way of handling errors when a 'findHasMany' call fails? Use case:
Model: App.User
{
DS.hasMany('comments', {'async': true});
}
Template
{{#each comment in comments}}
<p>{{comment.title}}</p>
{{/each}}
The issue is that when the lazy loading of comments fails, due to some server issue for example, I want to be able to respond to that error in the UI (by routing somewhere else, showing a popup about errors on the page, etc). At the moment the promise just rejects. I thought that Ember Data might have some hook on the ManyArray for cases like this, but it doesn't seem to, and the store seems to define precisely nothing as the action to carry out in such cases: https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/ember-data/lib/system/store.js#L1758 - the promise is given a 'resolve' method, but not a reject method.
My options seem to be either subclassing the store, and adding in some reject code there, or subclassing DS.PromiseArray and observing the 'isRejected' property. Any thoughts would be very welcome!
EDIT:
This issue seems to boil down to the fact that, when handling models defined in a route, Ember and Ember Data work well together (you can catch rejecting promises in an error action) there is no similar structure for async requests directly through a template. One solution might be to have an observer in the controller that observes something like 'model.isError', but a failing hasMany relationship does not trigger an error on the owning model. I suppose instead I can do 'comments.isRejected', but again, I would have to code that in for every controller that has a model with a hasMany relationship, in other words, all of them, which doesn't seem very satisfactory. If models had an observable enumerable property (like "hasManyIsError": {comments: false, posts: true}) then it would be easy to observe any of them with 'hasManyIsError.length
Assuming a var called user that has been fetched, you'd do this:
var itWorked = function(comments) { return comments; }
var itFailed = function(error) { return error; }
user.get("comments").then(itWorked, itFailed);
async: true means it'll get using a promise... so you can use then... you can't do that on a relationship that doesn't specify async: true.
[edit] sorry I just realised it might not be obvous that whatever you put in the itFailed function will eval when the request for comments fails, and likewise inversely for itWorked... :)
Premise: My question is based on my research of Ember-data, which may or may not be correct. So please correct me if I have any misunderstanding. The examples are running with the latest ember as of July 2, 2013.
To edit a record of my model, just 1 record, you need to call this.get('store').commit() or this.get('model').save(). However, downstream of either of these functions actually have to update all of the records of the model, even those left untouched. So this is quite inefficient, especially if the model has numerous records.
What's the best way to update one and only one record when I'm saving the edits of one record?
UPDATE: this problem only occurs for the local-storage-adapter, not the RESTAdapter.
UPDATE #2: I did have a huge misunderstanding. Everything is okay, save() and commit() both update just 1 record, I've been fooled by local storage adapter _saveData's JSON.stringify(this._data) which printed out all records. I assumed that whatever it printed out was the data that is changed, but turns out in _saveData's callers the records in updateRecords and _didSaveRecords were just the single record I was changing. The statements below about different objects containing "all records of the model" can no longer be duplicated. I guess I misread the debugging information.
It makes sense because _saveData uses localstorage, which currently can only setItem for an entire object, which in my case is the model containing all the records. Since localstorage can't update individual entries of that object, the JSON must contain all the records.
Details:
Running Examples:
this.get('store').commit() is used in doneEditing of updating a post this jsbin.
this.get('model').save() is used in acceptChanges of updating a todo this jsbin.
If you turn on Chrome debug and walk into the above two functions, you'll see something similar to below:
Downstream, there is currentTransaction or defaultTransaction, and both have all records of the model inside.
In the case of get('store').commit(), it eventually calls DS.Store's commit, which in turn calls: (see github)
get(this, 'defaultTransaction').commit();
In the case of case of get('model').save(), it eventualy calls DS.Store's scheduleSave and flushSavedRecords, which call: (see github)
get(this, 'currentTransaction').add(record);
get(this, 'currentTransaction').commit();
Note at the end a commit() is called on xxxTransaction, this is DS.Transaction's commit().
DS.Transactionscommit()has acommitDetails, which is based on xxxTransaction, socommitDetails` also has all the records of the data. (github)
Then DS.Adapter's commit and save are called and indeed every single record is updated (github):
this.groupByType(commitDetails.updated).forEach(function(type, set) {
this.updateRecords(store, type, filter(set));
}, this);
(minor side note/question: when was commitDetails set to "updated"?)
I know that DS.Adapter can be customized, but clearly the problem of saving more than one data (i.e. all of the model entries) are set from DS.Store's commitDefaultTransaction and currentTransaction.
Somehow I feel it would be a bad idea to tinker with DS.Store or anything upstream of DS.Adapter, like making my own version of save() and commit(). Basically I am reluctant to customize anything I'm told not to, since there might be ugly side effects.
So what should I do if I want to use Ember data but can only afford to update one record only at a time?
You can create a new transaction just for managing that record, using transaction() method of the store. This transaction has the same api as the defaultTransaction.
var transaction = this.get('store').transaction();
transaction.add(model)
transaction.commit();
Committing this transaction won't affect other changes. See this blog post for further ideas.
When new model is created and saved with RESTAdapter its Id property is undefined, because my backend is responsible for id generation. So when I return to grid there is 2 same entities: first - with empty Id (from RESTAdapter cache, I think) and second - with correct Id returned from backend.
Any ideas? Maybe it is posiible to disable RESTAdapter cache?
UPDATE
My code for entity creation.
submit:function () {
var manager = App.store.createRecord(App.Manager, {
firstName:this.get('firstName'),
lastName:this.get('lastName'),
speciality:this.get('speciality')
});
App.store.commit();
this.get('controller').transitionTo('managers');
return false;
}
NEW UPDATE
Thanks to Mike Grassotti hints. Here some details for my issue.
One antity have Id, another have no Id.
If I remove App.store.commit() code, there is no POST to server and only entity without Id will be displayed.
This entity has isLoaded=false and isError=true.
When new model is created and saved with RESTAdapter its Id property is undefined, because my backend is responsible for id generation.
Right, but there is nothing unusual about this - ember expects id generation to be done by the backend.
So when I return to grid there is 2 same entities: first - with empty Id (from RESTAdapter cache, I think) and second - with correct Id returned from backend.
OK. What do you mean by same 2 entities - surely they are different js objects. Try logging each of them to console like this:
console.log(entityOne.toString());
console.log(entityTwo.toString());
Any ideas?
There are many things that could cause this to happen. Sounds like somehow you are creating two new records and saving just one of them. Or could be the API response does not match what ember expects, causing an extra record to get created.
Try to enable logging on your records, then watch console so you can see what's going on as your model is saved. Hopefully this will give you some insight into when/how the extra record is being created.
record.set("stateManager.enableLogging", true)
Inspect browser communication with your api and compare JSON to see if it matches what the ember rest adapter expects.
Post that JSON and the rest of your source code (model definition, etc.) to Stack Overflow, maybe a second set of eyes will help.
Check this post for some other debugging tips: http://www.akshay.cc/blog/2013-02-22-debugging-ember-js-and-ember-data.html
Maybe it is posiible to disable RESTAdapter cache?
RESTAdapter does not maintain a separate cache of model objects. And since you are not trying to do anything special, there should be no need to take a step like that.
Many thanks to Mike Grassotti, I have found an answer to my question.
The good question was
With App.store.commit() back in, what does the JSON response from
server look like?
+1 for that comment.
I can't find any info in ember-data documentation for that, so some links still would be helpful for me. I change the result returned from backend and everything works fine now.
{
"manager": {
"firstName": "test",
"lastName": "test",
"speciality": "test",
"id": "acd325ac-03eb-419e-be8a-d4ac42e8c235"
}
}