Skip to content

Commit da458dd

Browse files
committed
Merge pull request #112 from ParsePlatform/grantland.revert
Publicize ParseObject.revert() and revert(key)
2 parents a0bc43e + 624e65c commit da458dd

File tree

2 files changed

+179
-12
lines changed

2 files changed

+179
-12
lines changed

Parse/src/main/java/com/parse/ParseObject.java

+22-12
Original file line numberDiff line numberDiff line change
@@ -836,23 +836,33 @@ public Set<String> keySet() {
836836
}
837837
}
838838

839-
/* package */ void revert(String key) {
839+
/**
840+
* Clears changes to this object's {@code key} made since the last call to {@link #save()} or
841+
* {@link #saveInBackground()}.
842+
*
843+
* @param key The {@code key} to revert changes for.
844+
*/
845+
public void revert(String key) {
840846
synchronized (mutex) {
841-
currentOperations().remove(key);
842-
rebuildEstimatedData();
843-
checkpointAllMutableContainers();
847+
if (isDirty(key)) {
848+
currentOperations().remove(key);
849+
rebuildEstimatedData();
850+
checkpointAllMutableContainers();
851+
}
844852
}
845853
}
846854

847855
/**
848856
* Clears any changes to this object made since the last call to {@link #save()} or
849857
* {@link #saveInBackground()}.
850858
*/
851-
/* package for tests */ void revert() {
859+
public void revert() {
852860
synchronized (mutex) {
853-
currentOperations().clear();
854-
rebuildEstimatedData();
855-
checkpointAllMutableContainers();
861+
if (isDirty()) {
862+
currentOperations().clear();
863+
rebuildEstimatedData();
864+
checkpointAllMutableContainers();
865+
}
856866
}
857867
}
858868

@@ -1448,10 +1458,10 @@ private ParseRESTObjectCommand currentSaveEventuallyCommand(
14481458
operationSetQueue.listIterator(operationSetQueue.indexOf(operationsBeforeSave));
14491459
opIterator.next();
14501460
opIterator.remove();
1451-
ParseOperationSet nextOperation = opIterator.next();
14521461

14531462
if (!success) {
14541463
// Merge the data from the failed save into the next save.
1464+
ParseOperationSet nextOperation = opIterator.next();
14551465
nextOperation.mergeFrom(operationsBeforeSave);
14561466
return task;
14571467
}
@@ -2862,8 +2872,8 @@ public List<T> then(Task<List<T>> task) throws Exception {
28622872
T newObject = resultMap.get(object.getObjectId());
28632873
if (newObject == null) {
28642874
throw new ParseException(
2865-
ParseException.OBJECT_NOT_FOUND,
2866-
"Object id " + object.getObjectId() + " does not exist");
2875+
ParseException.OBJECT_NOT_FOUND,
2876+
"Object id " + object.getObjectId() + " does not exist");
28672877
}
28682878
if (!Parse.isLocalDatastoreEnabled()) {
28692879
// We only need to merge if LDS is disabled, since single instance will do the merging
@@ -3526,7 +3536,7 @@ public boolean isDataAvailable() {
35263536
}
35273537
}
35283538

3529-
private boolean isDataAvailable(String key) {
3539+
/* package for tests */ boolean isDataAvailable(String key) {
35303540
synchronized (mutex) {
35313541
return isDataAvailable() || estimatedData.containsKey(key);
35323542
}

Parse/src/test/java/com/parse/ParseObjectTest.java

+157
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,28 @@
1111
import org.json.JSONArray;
1212
import org.json.JSONException;
1313
import org.json.JSONObject;
14+
import org.junit.After;
1415
import org.junit.Rule;
1516
import org.junit.Test;
1617
import org.junit.rules.ExpectedException;
1718

19+
import java.util.ArrayList;
1820
import java.util.Arrays;
21+
import java.util.Collections;
1922
import java.util.Date;
2023
import java.util.HashMap;
2124
import java.util.List;
2225
import java.util.Map;
2326
import java.util.Set;
2427

28+
import bolts.Task;
29+
2530
import static org.junit.Assert.assertEquals;
2631
import static org.junit.Assert.assertFalse;
2732
import static org.junit.Assert.assertNull;
2833
import static org.junit.Assert.assertTrue;
34+
import static org.mockito.Matchers.any;
35+
import static org.mockito.Matchers.anyString;
2936
import static org.mockito.Mockito.mock;
3037
import static org.mockito.Mockito.when;
3138

@@ -34,6 +41,11 @@ public class ParseObjectTest {
3441
@Rule
3542
public ExpectedException thrown = ExpectedException.none();
3643

44+
@After
45+
public void tearDown() {
46+
ParseCorePlugins.getInstance().reset();
47+
}
48+
3749
@Test
3850
public void testFromJSONPayload() throws JSONException {
3951
JSONObject json = new JSONObject(
@@ -72,6 +84,151 @@ public void testFromJSONPayloadWithoutClassname() throws JSONException {
7284
assertNull(parseObject);
7385
}
7486

87+
//region testRevert
88+
89+
@Test
90+
public void testRevert() throws ParseException {
91+
List<Task<Void>> tasks = new ArrayList<>();
92+
93+
// Mocked to let save work
94+
ParseCurrentUserController userController = mock(ParseCurrentUserController.class);
95+
when(userController.getAsync()).thenReturn(Task.<ParseUser>forResult(null));
96+
ParseCorePlugins.getInstance().registerCurrentUserController(userController);
97+
98+
// Mocked to simulate in-flight save
99+
Task<ParseObject.State>.TaskCompletionSource tcs = Task.create();
100+
ParseObjectController objectController = mock(ParseObjectController.class);
101+
when(objectController.saveAsync(
102+
any(ParseObject.State.class),
103+
any(ParseOperationSet.class),
104+
anyString(),
105+
any(ParseDecoder.class)))
106+
.thenReturn(tcs.getTask());
107+
ParseCorePlugins.getInstance().registerObjectController(objectController);
108+
109+
// New clean object
110+
ParseObject object = new ParseObject("TestObject");
111+
object.revert("foo");
112+
113+
// Reverts changes on new object
114+
object.put("foo", "bar");
115+
object.put("name", "grantland");
116+
object.revert();
117+
assertNull(object.get("foo"));
118+
assertNull(object.get("name"));
119+
120+
// Object from server
121+
ParseObject.State state = mock(ParseObject.State.class);
122+
when(state.className()).thenReturn("TestObject");
123+
when(state.objectId()).thenReturn("test_id");
124+
when(state.keySet()).thenReturn(Collections.singleton("foo"));
125+
when(state.get("foo")).thenReturn("bar");
126+
object = ParseObject.from(state);
127+
object.revert();
128+
assertFalse(object.isDirty());
129+
assertEquals("bar", object.get("foo"));
130+
131+
// Reverts changes on existing object
132+
object.put("foo", "baz");
133+
object.put("name", "grantland");
134+
object.revert();
135+
assertFalse(object.isDirty());
136+
assertEquals("bar", object.get("foo"));
137+
assertFalse(object.isDataAvailable("name"));
138+
139+
// Shouldn't revert changes done before last call to `save`
140+
object.put("foo", "baz");
141+
object.put("name", "nlutsenko");
142+
tasks.add(object.saveInBackground());
143+
object.revert();
144+
assertFalse(object.isDirty());
145+
assertEquals("baz", object.get("foo"));
146+
assertEquals("nlutsenko", object.get("name"));
147+
148+
// Should revert changes done after last call to `save`
149+
object.put("foo", "qux");
150+
object.put("name", "grantland");
151+
object.revert();
152+
assertFalse(object.isDirty());
153+
assertEquals("baz", object.get("foo"));
154+
assertEquals("nlutsenko", object.get("name"));
155+
156+
// Allow save to complete
157+
tcs.setResult(state);
158+
ParseTaskUtils.wait(Task.whenAll(tasks));
159+
}
160+
161+
@Test
162+
public void testRevertKey() throws ParseException {
163+
List<Task<Void>> tasks = new ArrayList<>();
164+
165+
// Mocked to let save work
166+
ParseCurrentUserController userController = mock(ParseCurrentUserController.class);
167+
when(userController.getAsync()).thenReturn(Task.<ParseUser>forResult(null));
168+
ParseCorePlugins.getInstance().registerCurrentUserController(userController);
169+
170+
// Mocked to simulate in-flight save
171+
Task<ParseObject.State>.TaskCompletionSource tcs = Task.create();
172+
ParseObjectController objectController = mock(ParseObjectController.class);
173+
when(objectController.saveAsync(
174+
any(ParseObject.State.class),
175+
any(ParseOperationSet.class),
176+
anyString(),
177+
any(ParseDecoder.class)))
178+
.thenReturn(tcs.getTask());
179+
ParseCorePlugins.getInstance().registerObjectController(objectController);
180+
181+
// New clean object
182+
ParseObject object = new ParseObject("TestObject");
183+
object.revert("foo");
184+
185+
// Reverts changes on new object
186+
object.put("foo", "bar");
187+
object.put("name", "grantland");
188+
object.revert("foo");
189+
assertNull(object.get("foo"));
190+
assertEquals("grantland", object.get("name"));
191+
192+
// Object from server
193+
ParseObject.State state = mock(ParseObject.State.class);
194+
when(state.className()).thenReturn("TestObject");
195+
when(state.objectId()).thenReturn("test_id");
196+
when(state.keySet()).thenReturn(Collections.singleton("foo"));
197+
when(state.get("foo")).thenReturn("bar");
198+
object = ParseObject.from(state);
199+
object.revert("foo");
200+
assertFalse(object.isDirty());
201+
assertEquals("bar", object.get("foo"));
202+
203+
// Reverts changes on existing object
204+
object.put("foo", "baz");
205+
object.put("name", "grantland");
206+
object.revert("foo");
207+
assertEquals("bar", object.get("foo"));
208+
assertEquals("grantland", object.get("name"));
209+
210+
// Shouldn't revert changes done before last call to `save`
211+
object.put("foo", "baz");
212+
object.put("name", "nlutsenko");
213+
tasks.add(object.saveInBackground());
214+
object.revert("foo");
215+
assertEquals("baz", object.get("foo"));
216+
assertEquals("nlutsenko", object.get("name"));
217+
218+
// Should revert changes done after last call to `save`
219+
object.put("foo", "qux");
220+
object.put("name", "grantland");
221+
object.revert("foo");
222+
assertEquals("baz", object.get("foo"));
223+
assertEquals("grantland", object.get("name"));
224+
225+
// Allow save to complete
226+
tcs.setResult(state);
227+
ParseTaskUtils.wait(Task.whenAll(tasks));
228+
}
229+
230+
//endregion
231+
75232
//region testGetter
76233

77234
@Test( expected = IllegalStateException.class )

0 commit comments

Comments
 (0)