ash-project/ash_paper_trail

Add restore and revert actions dependent on diff change tracking

Opened this issue · 9 comments

rgraff commented

RFC: For snapshot change tracking, add restore and reverts actions which would require a full diff and snapshot.

Normal validations and policies would apply to the source version

rgraff commented

Change tracking would require a full diff:

Proposed structure for an update change with snapshot:

{
   subject: { from: "subject", to: "new subject" },
   body: { unchanged: "body" }
   embedded_author: {
      first_name: { unchanged: "Jane" },
      last_name: { from: "Doh", to: "Doe" }
   } 
   embedded_tags: {
      "tag-3": {
         op: "create", 
         changes: { tag: { to: "ash" }   }
         index: { to: 0 }
      },
      "tag-1": {
         op: "update",
         changes: { tag: { unchanged: "phoenix" } },
         index: { from: 0, to: 1 }
      },
      "tag-2": {
         op: "destroy",
         changes: { tag: { unchanged: "nerves" } },
         index: { from: 1}
      }
   }
}

Same changes but changes only:

{
   subject: { from: "subject", to: "new subject" },
   // body is unchanged
   embedded_author: {
      last_name: { from: "Doh", to: "Doe" }
   } 
   embedded_tags: {
      "tag-3": {
         op: "create", 
         changes: { tag: { to: "ash" }   }
         index: { to: 0 }
      },
      "tag-1": {
         op: "update",
         // no changes, only moved index
         index: { from: 0, to: 1 }
      },
      "tag-2": {
         op: "destroy",
         index: { from: 1}
      }
   }
}

This will take some time to get right. One of the difficulties is that we're building a schema here that could ultimately need to change in the future which would be problematic. I'd also suggest potentially only storing the to here. The benefit of doing it this way is that any "stale" updates (i.e a an update action that uses an old version of a record that doesn't lock the record) won't affect the integrity of your log. We're going to be adding features to make stale updates not really an issue, but its something to keep in mind.

Only storing the to field makes sense, but having the restore API return both from and to will make it much easier to implement a nice GUI for it. I have some hand-rolled stuff for this in my legacy code:

2023-09-28T10:14:55,154175731-05:00

Yeah, that make sense. It will be more expensive, it would need to go back until it gets values for each field, or if its backed by ecto like AshPostgres maybe use a fancy query.

rgraff commented

The issue about storing from is that it's only valid if you lock the record on all updates. If you don't, you could get interleaved versions where neither has the correct from.

rgraff commented

👍 Something we could also do is add a change to lock records on all updates automatically, and explain that in the docs. In your case if they aren't written to frequently, that should be okay.

rgraff commented

Breaking this into three issues:

  • add a change_tracking_mode :full_diffthat works like above
  • add an undo_changes_action :undo option to create an action that will undo only the changes made in that version. The actual Change will be a module that can be manually added to a an action via a mixin.
  • add an restore_changes_action :restore option to create an action that will restore all fields in the changes hash to their state at the time of versioning. The Change can also just be manually added to an action via a mixin.
  • add a lock_on_update: true option to ensure integrating of the changes map if desired.