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.