Ember Data's Adapter saves edited records in different groups of Ember.OrderedSets, namely: commitDetails.created, commitDetails.updated, and commitDetails.deleted.
model.save() from model controller's createRecord() will be placed in the commitDetails.created group. model.save() from model controller's acceptChanges will placed be in the commitDetails.updated group. But I can't find in code where the placement association happens.
I know that they are instantiated in Ember Transaction's commit function (which calls Adapter's commit, in turn calling Adapter's save). Throughout this process, I can't figure out where exactly the records are sorted according to the created/updated/deleted criteria.
I'm not quite clear what you're asking, but if you're looking for where records get added to their appropriate commitDetails set, I believe this is the line you're looking for, in the commitDetails property itself.
Here's the relevant code.
forEach(records, function(record) {
if(!get(record, 'isDirty')) return;
record.send('willCommit');
var adapter = store.adapterForType(record.constructor);
commitDetails.get(adapter)[get(record, 'dirtyType')].add(record);
});
Let's walk through it.
forEach(records, function(record) {
if(!get(record, 'isDirty')) return;
The above says, for each record in the transaction, if it's not dirty, ignore it.
record.send('willCommit');
Otherwise, update its state to inFlight.
var adapter = store.adapterForType(record.constructor);
Get the record's adapter.
commitDetails.get(adapter)
Look up the adapter's created/updated/deleted trio object, which was instantiated at the top of this method here. It's simply an object with the 3 properties created, updated, and deleted, whose values are empty OrderedSets.
[get(record, 'dirtyType')]
Get the appropriate OrderedSet from the object we just obtained. For example, if the record we're on has been updated, get(record, 'dirtyType') will return the string updated. The brackets are just standard JavaScript property lookup, and so it grabs the updated OrderedSet from our trio object in the previous step.
.add(record);
Finally, add the record to the OrderedSet. On subsequent iterations of the loop, we'll add other records of the same type, so all created records get added to one set, all updated records get added to another set, and all deleted records get added to the third set.
What we end up with at the end of the entire method and return from the property is a Map whose keys are adapters, and whose values are these objects with the 3 properties created, updated, and deleted. Each of those, in turn, are OrderedSets of all the records in the transaction that have been created for that adapter, updated for that adapter, and deleted for that adapter, respectively.
Notice that this computed property is marked volatile, so it will get recomputed each time that someone gets the commitDetails property.
Related
Let's say I have a list of records that are supposed to have state selected in some current context. I can see two approaches here:
Create an array of objects: records.map(record => { record: record, selected: false }), get and set like `recordObj.set('selected', true). Render this array.
Set property explicitly on record objects: record.set('selected', true). Render RecordArray.
The second one looks much easier than the first as you don't have to manage additional array and objects (this becomes a real hassle).
The problem is that if you set anything on a record object, even a field that is not present in the model, it will still persist through the application (I guess until the model is reloaded and new record object is created?).
How to prevent that and ensure that temporary properties will be clean on every route change? Or how to improve the first approach? Is there anything I'm missing?
In route file
actions: {
willTransition() {
let records = this.get('controller.records');
records.setEach('selected', false);
}
}
This will make sure every time you leave route you are safe.
I implemented the Loggable extensions of Doctrine. But now I have the following case. I want to track "status" of object. But the objects already exist in my db with corresponding status for each of them. When I update one, in the log_entry is inserted the first log for an object with the new value for its status. (Let say I chnage status from active to suspended and in the log entry is inserted suspended)
From this moment I cannot revert the "active" status, becouse it is not recorded nowhere. I can deal with that with several ways, but is there some option for that Loggable Extension that instead of inserting new version, as a lest record for object in logs to store the current version, before changes happen?
You can override getObjectChangeSetData from LoggableListener.
Old values are stored there in $changes array.
In my application, the data in one model is "meta" in nature and defines the attributes in another model. Imagine retrieving a database, where the column definition data determines the attributes of each row.
I can reopen() the row model so long as I have not yet requested rows from the store. A subsequent call to store.findAll('row') retrieves data with all the attributes defined.
If however I have already called store.findAll('row') prior to Column.reopen(), then I cannot find a way cause the store to acknowledge the new attributes.
I have tried without success:
unloading all the rows and finding them again
calling peekAll/update on the rows already in the store
calling Ember.defineProperty() on the row model instead of reopen.
Both existing and subsequent new rows do not get the new attributes.
The solution that works for me is:
let store = this.get('store');
Ember.getOwner(store).unregister('model:item');
Item.reopen(newAttributes);
Ember.getOwner(store).register('model:item', Item);
let currentRoute = this.get('currentRoute');
if (currentRoute) {
currentRoute.refresh();
}
I register the route with the service that updates the schema. When the schema change is detected (which I debounce to avoid thrashing), I unregister the model, update it with reopen, re-register it and refresh the route. There may be a way to get the store to reflect these changes without refreshing the route, but I could not find it.
Good Morning,
why there are two methods, who returning the almost the same result.
I know "only", that the method Method.fetch() returns a promise.
What is the main difference between this two methods?
They return different results :) fetch returns a promise (as you said) immediately, which upon resolution will be your record. find returns a record immediately (possibly empty). If the record already exists client side then it will return that record, if it doesn't, it's an empty record, and once ember model finishes fetching the data it will populate the record with it's properties. They both have their pros/cons. fetch is safer for async programming if you depend on the record being populated before using it. find is convenient for immediate response time, with delayed population. Click run inside the examples a few times to view the differences.
Find
http://jsbin.com/UXOtOJI/20/edit
Fetch
http://jsbin.com/porozuno/1/edit
I am attempting to send a dirty record to the clean state manually (in relation to How to manually set an object state to clean (saved) using ember-data).
I've stumbled across something that may be happening because of undesirable use of ember-data or a bug.
Basically, what I do is
find the record in question,
.set() a property on the record, and
send the record to the 'becameClean' state manually. This is done in order to avoid having the record being committed when calling App.store.commit(); for reasons mentioned in the question linked to in the above.
Before we begin, I have added an enter: function() { console.log(this); console.log(this.get('path')); } line to the DS.State = ... part of ember-data in order to see which states the record goes through.
I'm running the very latest pull off of GitHub. Here's my approach:
Step 1) Call App.Fruit.find('banana');:
console.log(this); results in:
<DS.State:ember1077> { initialState="saved", isLoaded=true, saved=<DS.State:ember1078>, more...}
console.log(this.get('path')); results in:
'rootState.empty'
'rootState.loading'
'rootState.loaded'
'rootState.loaded.saved'
Step 2) Call App.Fruit.find('banana').set('description', 'Yellow fruit!');:
console.log(this); results in:
<(subclass of DS.State):ember1084> { dirtyType="updated", childStates=[3], eventTransitions={...}, more...}
console.log(this.get('path')); results in:
'rootState.loaded.updated'
Step 3) Call App.store.get('defaultTransaction.buckets');:
results in the record appearing in the 'updated' bucket
Step 4) Call App.Fruit.find('banana').get('stateManager').send('becameClean');:
console.log(this); results in:
<DS.State:ember1078> { childStates=[0], eventTransitions={...}, states={...}, more...}
console.log(this.get('path')); results in:
'rootState.loaded.saved'
Step 5) Call App.store.get('defaultTransaction.buckets');:
results in the record appearing in the 'clean' bucket
Intermission: Okay, so far, so good. It appears that I've successfully sent the record to the clean state. However, this happens:
Step 6) Call App.Fruit.find('banana').set('description', 'Even more yellow fruit!');:
console.log(this); results in:
(nothing)
console.log(this.get('path')); results in:
(nothing)
Step 7) Call App.store.get('defaultTransaction.buckets');:
results in the record appearing in the 'clean' bucket
The problem is that after I've sent the 'becameClean' state to the record, it stays in the 'loaded.saved' state no matter if I change the record afterwards.
When step 2 resulted in a new subclass object of DS.State being created with a dirtyType="updated", how come step 6 doesn't result in this too?
My question is: is this a bug or does it not work because my use of .send('becameClean') is undesirable?
The Short Answer
It is because manually triggering any event on a record's state manager is, as you put it, "undesirable." By doing so, you miss out on all of the other book keeping done by the record.
The only safe way to inject these new values from the server is with store.load(). (If you really want to know why, see below for all the gory details.) That will ensure the proper bookkeeping takes place.
Unfortunately this means it's up to you to make sure any uncommitted changes are safely stashed and re-applied after sideloading (as sideloading will replace all attributes on the record).
The Long Answer
In this case, manually marking a record as clean breaks a couple things internally: (1) the record's list of dirty factors is left intact, and (2) the record's copy of the original attributes is unchanged.
(1) Dirty factors are those properties—attributes and associations—that have changed since the last commit. The record uses this list to, among other things, decide if the record needs to transition to a dirty state. When you set a property, e.g., description, it checks its list of dirty factors to see if that property has already been modified. If it hasn't, and if the record is currently considered "clean," it transitions the record to the dirty state.
In your example, you modified the description, then manually marked the record as clean. The record, however, still thought its description was dirty, so when you went to change it a second time, it never bothered to transition to the dirty state—it thought it was already there.
(2) You could technically use record.removeDirtyFactors() to flush the dirty factors and transition the record to the clean state, but even if you did, the record's copy of the original attributes would still be wrong. It would think the server had "A" when it was actually "B". If you then tried to change it back to "A" in the client and commit, the record wouldn't do anything—it would think it was already back in sync with the server.