How do I undo changed to an un-synced collection?

103 Views Asked by At

How am I able to undo un-synced changes to the database?

A use case scenario

I want to give the user the possibility to undo a database operation (i.e. deletion) for at least a few seconds after he does one.

A possibility would be to hold on for the deletion from the database until the time to undo it passes, however I think it would be more streamlined to reflect in the code what I'll see in the UI, just to keep things 1:1.

So, I tried storing the object before deletion and then updating it (so that its _status wouldn't be deleted anymore):

 this.lastDeletedDoc = this.docs[this.lastDeletedDocIndex];

 // remove from the db
 this.documents.delete(docId)
  .then(console.log.bind(console))
  .catch(console.error.bind(console));

// ...

// user taps "UNDO"
this.documents.update(this.lastDeletedDoc)
  .then(console.log.bind(console))
  .catch(console.error.bind(console));

but I got the error Error: Record with id=65660f62-3eb1-47b7-8746-5d0b2ef44eeb not found.

I also tried creating the object again with:

// user taps "UNDO"
this.documents.create(this.lastDeletedDoc, { useRecordId: true })
   .then(console.log.bind(console))
   .catch(console.error.bind(console));

but I get an Id already present error.

I also skimmed trough the source code quickly, but couldn't find any undo functions.

How do I generally undo changes to an un-synced kinto collection?

2

There are 2 best solutions below

0
On BEST ANSWER

As such, you should be able to find the record back and set its _status to its old previous version, like you are doing.

The problem lies in the fact that the get method takes an includeDeleted option, which allows you to retrieve deleted records, but the update method doesn't pass it this option.

The best way to fix this is probably to open a pull request on the Kinto.js repository, making the update method accept an includeDeleted option, that it would pass to the get method.

Due to a limited connection right now I cannot push the change, but it would look basically like this (+ a test which demonstrates this works properly):

diff --git a/src/collection.js b/src/collection.js
index c0cce02..a0bf0e4 100644
--- a/src/collection.js
+++ b/src/collection.js
@@ -469,7 +469,7 @@ export default class Collection {
    * @param  {Object} options
    * @return {Promise}
    */
-  update(record, options={synced: false, patch: false}) {
+  update(record, options={synced: false, patch: false, includeDeleted:false}) {
     if (typeof(record) !== "object") {
       return Promise.reject(new Error("Record is not an object."));
     }
@@ -479,7 +479,7 @@ export default class Collection {
     if (!this.idSchema.validate(record.id)) {
       return Promise.reject(new Error(`Invalid Id: ${record.id}`));
     }
-    return this.get(record.id)
+    return this.get(record.id, {includeDeleted: options.includeDeleted})
       .then((res) => {
         const existing = res.data;
         const newStatus = options.synced ? "synced" : "updated";

Don't hesitate to submit a pull request with these changes, I believe that should solve your problem!

2
On

I'm not sure whether combining 'unsynced' with 'user-can-undo' is a good design principle. If you are sure you will only ever want to undo deletions, then it would work to piggyback your undo functionality on the sync delay in this way, but what if in the future you want to support undoing updates? The old value would already be lost.

I think what you should do in your app is add a collection called 'undo-history', where you store objects with all the data needed for undoing user operations. If you sync this collection, then it will even be possible to delete something on your phone, and then undo that from your laptop! :)