1

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

  1. find the record in question,
  2. .set() a property on the record, and
  3. 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:

    1. 'rootState.empty'
    2. 'rootState.loading'
    3. 'rootState.loaded'
    4. '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:

    1. '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:

    1. '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?

Community
  • 1
  • 1
Kasper Tidemann
  • 488
  • 1
  • 3
  • 15
  • Kasper has provided an answer in his blog, so to complete the loop, here is the link: **http://www.kaspertidemann.com/how-to-manually-change-the-state-of-a-dirtied-ember-data-object/** – brg Nov 16 '12 at 07:14
  • 1
    You can debug a record's state transitions more easily by setting a property on its state manager: `record.set('stateManager.enableLogging', true)` – Ian Lesperance Nov 17 '12 at 15:44

1 Answers1

1

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.

Ian Lesperance
  • 4,961
  • 1
  • 26
  • 28
  • I've tried calling `removeDirtyFactors()` on my `banana` record. Say the description of the record is `"A"` at first. Now, if I change it to `"B"`, call `removeDirtyFactors()` on the record and commit the changes, this does indeed *not* result in a commit of the record. However, if I then change the description back to `"A"` (the previous value) and call `App.store.commit()`, this doesn't result in the change being committed. Puzzling. – Kasper Tidemann Nov 18 '12 at 01:48
  • 1
    Dang. Turns out there's an internal cache of the attributes that is out of sync with your "cleaned" version. That is, it thinks the server-side value is still "A". That's preventing it from marking the attribute as dirty when you go from "B" to "A" again. I've updated my answer to account for this. – Ian Lesperance Nov 18 '12 at 04:28