Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added DS.Transaction#rollback()

Transactions now have a rollback() method that
causes all of the records that belong to them to
be reset to their state when they were first added
to the transaction.

The only exception to this is newly created
states, which immediately become deleted upon
transaction rollback.
  • Loading branch information...
commit e6c44b5113b529156bfc8c1f2fc81f5feac59e70 1 parent 75b1bb0
tomhuda authored
View
194 diagrams/Ember Data diagrams.graffle
@@ -26,7 +26,7 @@
<key>MasterSheets</key>
<array/>
<key>ModificationDate</key>
- <string>2012-02-17 00:06:46 +0000</string>
+ <string>2012-04-17 22:16:22 +0000</string>
<key>Modifier</key>
<string>tomhuda</string>
<key>NotesVisible</key>
@@ -2763,8 +2763,8 @@ committing}</string>
<integer>18</integer>
<key>Points</key>
<array>
- <string>{715.69598, 239.98688}</string>
- <string>{750.29596, 239.98465}</string>
+ <string>{715.69598, 239.98952}</string>
+ <string>{750.29596, 239.98843}</string>
</array>
<key>Style</key>
<dict>
@@ -2796,8 +2796,8 @@ committing}</string>
<integer>24</integer>
<key>Points</key>
<array>
- <string>{715.69598, 343.98685}</string>
- <string>{750.29596, 343.98462}</string>
+ <string>{715.69598, 343.98947}</string>
+ <string>{750.29596, 343.9884}</string>
</array>
<key>Style</key>
<dict>
@@ -2829,8 +2829,8 @@ committing}</string>
<integer>26</integer>
<key>Points</key>
<array>
- <string>{715.69598, 447.98685}</string>
- <string>{750.29596, 447.98462}</string>
+ <string>{715.69598, 447.98947}</string>
+ <string>{750.29596, 447.9884}</string>
</array>
<key>Style</key>
<dict>
@@ -2862,8 +2862,8 @@ committing}</string>
<integer>34</integer>
<key>Points</key>
<array>
- <string>{715.69598, 551.98676}</string>
- <string>{750.29596, 551.98456}</string>
+ <string>{715.69598, 551.98944}</string>
+ <string>{750.29596, 551.98834}</string>
</array>
<key>Style</key>
<dict>
@@ -2895,8 +2895,8 @@ committing}</string>
<integer>36</integer>
<key>Points</key>
<array>
- <string>{715.69598, 655.98676}</string>
- <string>{750.29596, 655.98456}</string>
+ <string>{715.69598, 655.98944}</string>
+ <string>{750.29596, 655.98834}</string>
</array>
<key>Style</key>
<dict>
@@ -2928,8 +2928,8 @@ committing}</string>
<integer>44</integer>
<key>Points</key>
<array>
- <string>{715.69598, 759.98676}</string>
- <string>{750.29596, 759.98456}</string>
+ <string>{715.69598, 759.98944}</string>
+ <string>{750.29596, 759.98834}</string>
</array>
<key>Style</key>
<dict>
@@ -2961,8 +2961,8 @@ committing}</string>
<integer>46</integer>
<key>Points</key>
<array>
- <string>{715.69598, 863.98676}</string>
- <string>{750.29596, 863.98456}</string>
+ <string>{715.69598, 863.98944}</string>
+ <string>{750.29596, 863.98834}</string>
</array>
<key>Style</key>
<dict>
@@ -2994,8 +2994,8 @@ committing}</string>
<integer>48</integer>
<key>Points</key>
<array>
- <string>{715.69598, 967.99023}</string>
- <string>{750.29596, 967.98944}</string>
+ <string>{715.69598, 967.99115}</string>
+ <string>{750.29596, 967.99078}</string>
</array>
<key>Style</key>
<dict>
@@ -3027,8 +3027,8 @@ committing}</string>
<integer>50</integer>
<key>Points</key>
<array>
- <string>{715.69598, 1071.9949}</string>
- <string>{750.29596, 1071.9962}</string>
+ <string>{715.69598, 1071.9934}</string>
+ <string>{750.29596, 1071.994}</string>
</array>
<key>Style</key>
<dict>
@@ -3060,8 +3060,8 @@ committing}</string>
<integer>52</integer>
<key>Points</key>
<array>
- <string>{715.69598, 1175.9949}</string>
- <string>{750.29596, 1175.9962}</string>
+ <string>{715.69598, 1175.9934}</string>
+ <string>{750.29596, 1175.994}</string>
</array>
<key>Style</key>
<dict>
@@ -3093,8 +3093,8 @@ committing}</string>
<integer>54</integer>
<key>Points</key>
<array>
- <string>{715.69598, 1279.9949}</string>
- <string>{750.29596, 1279.9962}</string>
+ <string>{715.69598, 1279.9934}</string>
+ <string>{750.29596, 1279.994}</string>
</array>
<key>Style</key>
<dict>
@@ -3179,6 +3179,13 @@ committing}</string>
<string>{{0, 0}, {1466, 1728}}</string>
<key>Class</key>
<string>SolidGraphic</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>11</real>
+ </dict>
<key>ID</key>
<integer>2</integer>
<key>Style</key>
@@ -3207,6 +3214,99 @@ committing}</string>
<array>
<dict>
<key>Bounds</key>
+ <string>{{527.79272, 318.95544}, {57, 21}}</string>
+ <key>Class</key>
+ <string>ShapedGraphic</string>
+ <key>FitText</key>
+ <string>YES</string>
+ <key>Flow</key>
+ <string>Resize</string>
+ <key>FontInfo</key>
+ <dict>
+ <key>Color</key>
+ <dict>
+ <key>w</key>
+ <string>0</string>
+ </dict>
+ <key>Font</key>
+ <string>Helvetica</string>
+ <key>Size</key>
+ <real>9</real>
+ </dict>
+ <key>ID</key>
+ <integer>73</integer>
+ <key>Line</key>
+ <dict>
+ <key>ID</key>
+ <integer>72</integer>
+ <key>Position</key>
+ <real>0.68339651823043823</real>
+ <key>RotationType</key>
+ <integer>0</integer>
+ </dict>
+ <key>Shape</key>
+ <string>Rectangle</string>
+ <key>Style</key>
+ <dict>
+ <key>shadow</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ <key>stroke</key>
+ <dict>
+ <key>Draws</key>
+ <string>NO</string>
+ </dict>
+ </dict>
+ <key>Text</key>
+ <dict>
+ <key>Text</key>
+ <string>{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320
+{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
+{\colortbl;\red255\green255\blue255;}
+\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc
+
+\f0\fs18 \cf0 didRollback}</string>
+ </dict>
+ <key>Wrap</key>
+ <string>NO</string>
+ </dict>
+ <dict>
+ <key>Class</key>
+ <string>LineGraphic</string>
+ <key>Head</key>
+ <dict>
+ <key>ID</key>
+ <integer>35</integer>
+ </dict>
+ <key>ID</key>
+ <integer>72</integer>
+ <key>Points</key>
+ <array>
+ <string>{566.69312, 394.24487}</string>
+ <string>{551.47443, 299.43985}</string>
+ </array>
+ <key>Style</key>
+ <dict>
+ <key>stroke</key>
+ <dict>
+ <key>HeadArrow</key>
+ <string>FilledArrow</string>
+ <key>LineType</key>
+ <integer>1</integer>
+ <key>TailArrow</key>
+ <string>0</string>
+ </dict>
+ </dict>
+ <key>Tail</key>
+ <dict>
+ <key>ID</key>
+ <integer>17</integer>
+ </dict>
+ </dict>
+ <dict>
+ <key>Bounds</key>
<string>{{701.57288, 91.389076}, {112.49997, 52.5}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
@@ -3770,7 +3870,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{405.45905, 243.88972}, {36, 15}}</string>
+ <string>{{336.71387, 280.45663}, {36, 15}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -3838,19 +3938,21 @@ committing}</string>
<key>ControlPoints</key>
<array>
<string>{0, 116.88016}</string>
- <string>{15.330688, -91.972809}</string>
+ <string>{-193.13083, 18.796387}</string>
</array>
<key>Head</key>
<dict>
<key>ID</key>
- <integer>17</integer>
+ <integer>16</integer>
+ <key>Info</key>
+ <integer>1</integer>
</dict>
<key>ID</key>
<integer>56</integer>
<key>Points</key>
<array>
<string>{357.02802, 143.8891}</string>
- <string>{551.59241, 395.819}</string>
+ <string>{510.4201, 417.81549}</string>
</array>
<key>Style</key>
<dict>
@@ -4078,7 +4180,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{551.18799, 496.25378}, {51, 21}}</string>
+ <string>{{551.0542, 496.25592}, {51, 21}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -4148,8 +4250,8 @@ committing}</string>
<integer>47</integer>
<key>Points</key>
<array>
- <string>{572.75403, 447.65198}</string>
- <string>{581.2616, 575.46606}</string>
+ <string>{572.71234, 447.65259}</string>
+ <string>{581.02081, 575.46997}</string>
</array>
<key>Style</key>
<dict>
@@ -4175,7 +4277,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{647.79327, 495.57224}, {60, 21}}</string>
+ <string>{{647.35614, 495.69312}, {60, 21}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -4245,8 +4347,8 @@ committing}</string>
<integer>45</integer>
<key>Points</key>
<array>
- <string>{769.56244, 579.23114}</string>
- <string>{599.78912, 443.88687}</string>
+ <string>{769.45844, 579.26044}</string>
+ <string>{599.06879, 444.08563}</string>
</array>
<key>Style</key>
<dict>
@@ -4603,7 +4705,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{633.52472, 413.41568}, {48, 15}}</string>
+ <string>{{633.52472, 413.44717}, {48, 15}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -4677,8 +4779,8 @@ committing}</string>
<integer>31</integer>
<key>Points</key>
<array>
- <string>{627.72443, 420.91568}</string>
- <string>{699.41046, 420.91571}</string>
+ <string>{627.72443, 420.93631}</string>
+ <string>{699.41052, 420.96243}</string>
</array>
<key>Style</key>
<dict>
@@ -5182,7 +5284,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{269.87964, 40.195496}, {837.04358, 1084.7268}}</string>
+ <string>{{270, 36}, {837, 1089}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FontInfo</key>
@@ -5419,7 +5521,7 @@ committing}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{252.92319, 443.26813}, {48, 21}}</string>
+ <string>{{252.92319, 443.2688}, {48, 21}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -5489,8 +5591,8 @@ committing}</string>
<integer>17</integer>
<key>Points</key>
<array>
- <string>{235.11548, 453.76843}</string>
- <string>{337.9614, 453.76767}</string>
+ <string>{235.11548, 453.76886}</string>
+ <string>{337.9614, 453.76874}</string>
</array>
<key>Style</key>
<dict>
@@ -5673,7 +5775,7 @@ setData}</string>
</dict>
<dict>
<key>Bounds</key>
- <string>{{245.44879, 235.92339}, {59, 32}}</string>
+ <string>{{245.44879, 235.92314}, {59, 32}}</string>
<key>Class</key>
<string>ShapedGraphic</string>
<key>FitText</key>
@@ -5744,8 +5846,8 @@ materialized}</string>
<integer>7</integer>
<key>Points</key>
<array>
- <string>{235.11546, 251.92328}</string>
- <string>{337.9614, 251.92354}</string>
+ <string>{235.11546, 251.92313}</string>
+ <string>{337.9614, 251.92317}</string>
</array>
<key>Style</key>
<dict>
@@ -5892,9 +5994,9 @@ materialized}</string>
<key>SidebarWidth</key>
<integer>120</integer>
<key>VisibleRegion</key>
- <string>{{0.76923084, 0.76923084}, {1028.4615, 920.76929}}</string>
+ <string>{{-295.46155, -53.923157}, {2056.9233, 1840}}</string>
<key>Zoom</key>
- <real>1.2999999523162842</real>
+ <real>0.64999997615814209</real>
<key>ZoomValues</key>
<array>
<array>
@@ -5914,8 +6016,8 @@ materialized}</string>
</array>
<array>
<string>Canvas 4</string>
+ <real>0.64999997615814209</real>
<real>1.2999999523162842</real>
- <real>1.2200000286102295</real>
</array>
<array>
<string>Canvas 5</string>
View
2  packages/ember-data/lib/system/model/data_proxy.js
@@ -84,6 +84,8 @@ DataProxy.prototype = {
rollback: function() {
this.unsavedData = {};
+
+ this.record.notifyPropertyChange('data');
},
adapterDidUpdate: function(data) {
View
34 packages/ember-data/lib/system/model/states.js
@@ -362,6 +362,20 @@ var DirtyState = DS.State.extend({
willCommit: function(manager) {
manager.goToState('inFlight');
+ },
+
+ rollback: function(manager) {
+ var record = get(manager, 'record'),
+ dirtyType = get(this, 'dirtyType'),
+ data = get(record, 'data');
+
+ data.rollback();
+
+ record.withTransaction(function(t) {
+ t.recordBecameClean(dirtyType, record);
+ });
+
+ manager.goToState('loaded');
}
}, Uncommitted),
@@ -557,6 +571,15 @@ var updatedState = DirtyState.create({
createdState.states.uncommitted.reopen(CreatedUncommitted);
createdState.states.pending.states.uncommitted.reopen(CreatedUncommitted);
+// The created.uncommitted state needs to immediately transition to the
+// deleted state if it is rolled back.
+createdState.states.uncommitted.reopen({
+ rollback: function(manager) {
+ this._super(manager);
+ manager.goToState('deleted.saved');
+ }
+});
+
// The updated.uncommitted state and updated.pending.uncommitted share
// some logic defined in UpdatedUncommitted.
updatedState.states.uncommitted.reopen(UpdatedUncommitted);
@@ -696,6 +719,17 @@ var states = {
// EVENTS
willCommit: function(manager) {
manager.goToState('inFlight');
+ },
+
+ rollback: function(manager) {
+ var record = get(manager, 'record'),
+ data = get(record, 'data');
+
+ data.rollback();
+ record.withTransaction(function(t) {
+ t.recordBecameClean('deleted', record);
+ });
+ manager.goToState('loaded');
}
}),
View
44 packages/ember-data/lib/system/transaction.js
@@ -144,5 +144,49 @@ DS.Transaction = Ember.Object.extend({
if (adapter && adapter.commit) { adapter.commit(store, commitDetails); }
else { throw fmt("Adapter is either null or do not implement `commit` method", this); }
+ },
+
+ /**
+ Rolling back a transaction resets the records that belong to
+ that transaction.
+
+ Updated records have their properties reset to the last known
+ value from the persistence layer. Deleted records are reverted
+ to a clean, non-deleted state. Newly created records immediately
+ become deleted, and are not sent to the adapter to be persisted.
+
+ After the transaction is rolled back, any records that belong
+ to it will return to the store's default transaction, and the
+ current transaction should not be used again.
+ */
+ rollback: function() {
+ var buckets = get(this, 'buckets'),
+ store = get(this, 'store'),
+ dirty;
+
+ // Loop through all of the records in each of the dirty states
+ // and initiate a rollback on them. As a side effect of telling
+ // the record to roll back, it should also move itself out of
+ // the dirty bucket and into the clean bucket.
+ ['created', 'updated', 'deleted'].forEach(function(dirtyType) {
+ dirty = get(buckets, dirtyType);
+
+ dirty.forEach(function(type, records) {
+ records.forEach(function(record) {
+ record.send('rollback');
+ });
+ });
+ });
+
+ // Now that all records in the transaction are guaranteed to be
+ // clean, migrate them all to the store's default transaction.
+ var clean = get(buckets, 'clean');
+ var defaultTransaction = get(store, 'defaultTransaction');
+
+ clean.forEach(function(type, records) {
+ records.forEach(function(record) {
+ this.remove(record);
+ }, this);
+ }, this);
}
});
View
58 packages/ember-data/tests/unit/transaction_test.js
@@ -212,3 +212,61 @@ test("a record that is in the clean state is moved back to the default transacti
equal(commitCalled, 1, "should attempt to commit records");
equal(get(person, 'transaction'), get(store, 'defaultTransaction'), "record should have been moved back to the default transaction");
});
+
+test("modified records are reset when their transaction is rolled back", function() {
+
+ var store = DS.Store.create({
+ adapter: DS.Adapter.create({
+ commit: function() {
+ ok(false, "should never call adapter methods");
+ }
+ })
+ });
+
+ store.load(Person, { id: 1, name: "Scumbag Tom" });
+ store.load(Person, { id: 2, name: "Scumbag Carl" });
+ store.load(Person, { id: 3, name: "Scumbag André" });
+
+ var updatedPerson = store.find(Person, 1);
+ var deletedPerson = store.find(Person, 2);
+ var anotherUpdatedPerson = store.find(Person, 3);
+
+ var transaction = store.transaction();
+ transaction.add(updatedPerson);
+ transaction.add(deletedPerson);
+ transaction.add(anotherUpdatedPerson);
+
+ var newPerson = transaction.createRecord(Person, {
+ name: "Scumbag Yehuda"
+ });
+
+ updatedPerson.set('name', "Scumbag Patrick");
+ anotherUpdatedPerson.set('name', "Scumbag Leah");
+ deletedPerson.deleteRecord();
+
+ equal(updatedPerson.get('isDirty'), true, "precond - Record is marked as dirty when changed");
+ equal(updatedPerson.get('name'), "Scumbag Patrick", "precond - Record has been changed to the value we set");
+ equal(anotherUpdatedPerson.get('isDirty'), true, "precond - Record is marked as dirty when changed");
+ equal(anotherUpdatedPerson.get('name'), "Scumbag Leah", "precond - Record has been changed to the value we set");
+ equal(newPerson.get('isDirty'), true, "precond - new record is marked as dirty");
+ equal(newPerson.get('isNew'), true, "precond - new record is marked as new");
+ equal(deletedPerson.get('isDirty'), true, "precond - deleted record is marked as dirty when deleted");
+ equal(deletedPerson.get('isDeleted'), true, "precond - deleted record is marked as deleted");
+
+ transaction.rollback();
+
+ equal(updatedPerson.get('isDirty'), false, "Record is not dirty after rollback");
+ equal(updatedPerson.get('name'), "Scumbag Tom", "Record has previously loaded name");
+ equal(anotherUpdatedPerson.get('isDirty'), false, "Record is not dirty after rollback");
+ equal(anotherUpdatedPerson.get('name'), "Scumbag André", "Record has previously loaded name");
+ equal(newPerson.get('isDirty'), false, "created record is no longer considered dirty");
+ equal(newPerson.get('isDeleted'), true, "created records are deleted when their transaction is rolled back");
+ equal(deletedPerson.get('isDirty'), false, "deleted record is no longer considered dirty");
+ equal(deletedPerson.get('isDeleted'), false, "deleted record is no longer considered deleted");
+
+ equal(get(newPerson, 'transaction'), get(store, 'defaultTransaction'), "record should have been moved back to the default transaction");
+ equal(get(updatedPerson, 'transaction'), get(store, 'defaultTransaction'), "record should have been moved back to the default transaction");
+ equal(get(anotherUpdatedPerson, 'transaction'), get(store, 'defaultTransaction'), "record should have been moved back to the default transaction");
+ equal(get(deletedPerson, 'transaction'), get(store, 'defaultTransaction'), "record should have been moved back to the default transaction");
+});
+
Please sign in to comment.
Something went wrong with that request. Please try again.