24

Explanation:

I'm using ember-data for a project of mine and I have a question that revolves around the possibility of dirtying an object and then setting its state to clean again on purpose - without commiting the changes. The scenario is this:

Say I've fetched an object via banana = App.Fruit.find('banana'); and it has a description of "Yellow fruit!". Using XHR long-polling (or WebSockets), I may receive an updated version of the object because of another user having changed the description to "A tasty yellow fruit!" at any given point in time after I fetched the original object.

Then, what I would like to do is to update the object to reflect the newly received data. For this, I've tried different approaches:

  • I've tried calling App.Store.load(App.Fruit, new_data);. First of all, this approach doesn't work and secondly, this is not really what I want. I could've made uncommitted changes to the object myself and in this case, it would be undesirable to just discard those (assuming the load() call would overwrite them).

  • I've tried looping through the new data, calling .set() - like so: banana.set('description', new_data.description); - in order to update the object properties with the new data (where applicable = not dirty). This works but it leaves the object in a dirtied state.

In order to make the object clean/updated again - and not have the adapter commit the changes! - I've taken a look at the states the object travels through. These are (at least):

  • Step 1: Initially, the object is in the rootState.loaded.saved state.
  • Step 2: Calling .set() on a property pushes it to the rootState.loaded.updated.uncommitted state.
  • Step 3: Calling App.store.commit(); returns the object to the rootState.loaded.saved state.

Therefore, I've tried to manually set the object state to saved after step 2 like so: banana.get('stateManager').goToState('saved');.

However, this doesn't work. The next time the store commits for any other reason, this maneuver produces an inFlightDirtyReasons is undefined error.

Question:

My question is: how can I manually change the state of a dirtied object back to clean (saved) again?

Kasper Tidemann
  • 488
  • 1
  • 3
  • 15

13 Answers13

24

Solution for Ember Data 1.0.0-beta.7:

// changing to loaded.updated.inFlight, which has "didCommit" 
record.send('willCommit'); 
// clear array of changed (dirty) model attributes
record.set('_attributes', {});
// changing to loaded.saved (hooks didCommit event in "inFlight" state)
record.send('didCommit');

I've searched the source code of Ember-data and I've found that loaded.saved state has a setup function that checks whether a model is clean, before setting "saved" state. If it is not clean, then it rejects a request to change state and returns to loaded.updated.uncommitted.

So you have to clean model._attributes array, which keeps attributes names and Ember will let you change state manually.

I know it isn't very good solution, because is needed to set private property of a model, but I've not found any other solutions yet.

Kamil J.
  • 383
  • 1
  • 2
  • 7
12

Looking at ember-data the uncommitted state has a 'becameClean' event which consequently sets the record as loaded.saved.

This should do the trick

record.get('stateManager').send('becameClean');  
Loadx
  • 250
  • 2
  • 4
10

Solution for Ember Data 2.6.1

record.send('pushedData');

set dirty record as loaded and saved

https://github.com/emberjs/data/blob/fec260a38c3f7227ffe17a3af09973ce2718acca/addon/-private/system/model/states.js#L250

elatonsev
  • 699
  • 6
  • 8
7

It's an update to @Kamil-j's solution.

For Ember Data 2.0 which I am currently using I have to do the following:

record._internalModel.send('willCommit'); 
record._internalModel._attributes = {};
record._internalModel.send('didCommit');
Chris Peters
  • 17,918
  • 6
  • 49
  • 65
Serge
  • 333
  • 2
  • 9
5

As of 1.0.0.rc6.2....

This will move a model into the state of a model that has been saved.

record.get('stateManager').transitionTo('loaded.saved') 

This will moves a model to a the state of a new model that has not been committed. Think new dirty model.

record.get('stateManager').transitionTo('loaded.created.uncommitted')

This will move a model into the sate of an old model that has been updated, think old dirty model:

record.get('stateManager').transitionTo('loaded.updated')
user160917
  • 9,211
  • 4
  • 53
  • 63
  • I'm pretty sure it's "loaded.created.uncommitted", not "loaded.created.uncommited". Notice an extra "t" in "uncommitted". – yuяi Jul 08 '13 at 18:18
  • Just a quick headsup that this doesn't work in ember rc7. Not sure what the alternative is. Still looking.... – i0n Aug 23 '13 at 14:29
5

As of ember-data 1.0.0-beta.12:

record.transitionTo('loaded.saved');

It seems that record.get('stateManager') is not required anymore.

Mad Angle
  • 2,347
  • 1
  • 15
  • 33
Aymerick
  • 51
  • 1
  • 1
4

Here's what seems to work for Ember Data 1.0.0-beta.10:

record.set('currentState.stateName', 'root.loaded.saved');
record.adapterWillCommit();
record.adapterDidCommit();
record.set('currentState.isDirty', false);

Not sure if all those lines are required but just following what others have done prior to this.

3

Ember 2.9.1

record.set('currentState.isDirty', false);
brayancastrop
  • 381
  • 5
  • 17
2

Tested on Ember Data 2.9

pushedData action is the way to go but besides that the "originalValues" need to be reset as well.

Ember.assign(record.data, record._internalModel._attributes);
Ember.assign(record._internalModel._data, record._internalModel._attributes);
record.send('pushedData');
Martin Malinda
  • 1,573
  • 11
  • 20
1

It looks like with newer versions everything methioned here got broken.

This worked for me with ember-data 1.0.0.beta4:

record.adapterWillCommit();
record.adapterDidCommit();
Andriy Buday
  • 1,959
  • 1
  • 17
  • 40
1

Another method that worked for me when using Ember Data 1.0.0-beta.18:

record.rollback()

This reversed the dirty attributes and returned the record to a clean state.

Seems like this may have been since deprecated in favor of record.rollbackAttributes: http://emberjs.com/api/data/classes/DS.Model.html#method_rollbackAttributes

Jeff E
  • 181
  • 11
1

I work on Ember data 1.13 so I used the following solution (which seems a mix between the one provided by @Martin Malinda and the other by @Serge):

// Ensure you have the changes inside the record
Object.assign(record.data, record._internalModel._attributes);
Object.assign(record._internalModel._data,record._internalModel._attributes);

// Using the DS.State you can first simulate the record is going to be saved
record.get('_internalModel').send('willCommit');

// Cleaning the prevous dirty attributes
record.get('_internalModel')._attributes = {};

// Mark the record as saved (root.loaded.created.uncommitted) even if it isn't for real
record.get('_internalModel').send('didCommit');

In this way, if we will call a further rollbackAttributes() on this record, if we will have some dirty attributes, the record will be reset to this last state (instead of having the original properties) which was exactly what I was looking for in my use case.

If we won't have any dirty attributes, nothing will change and we will keep the last attributes set using this code without having them rolled back to the original ones. Hope it helps.

AndreaScn
  • 83
  • 1
  • 9
1

Tested on Ember Data 3.8.0

Just an update to Martin Malinda's answer:

// Clear changed attributes list 
record._internalModel._recordData._attributes = {};

// Trigger transition to 'loaded.saved' state
record.send('pushedData');

In my case I also needed to override serializer's normalize method.

vvlnv
  • 394
  • 3
  • 9