New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best way to implement updates from cloned data? #22

Closed
markerikson opened this Issue May 31, 2016 · 4 comments

Comments

Projects
None yet
3 participants
@markerikson
Collaborator

markerikson commented May 31, 2016

Redux-ORM has worked great for parsing and displaying data in my app so far, but I'm now up to where I need to re-implement editing behavior in this app rewrite. I need to be able to continue to show the "original" data in one portion of my UI, show the "work-in-progress" data for the item being edited in a form and in another component, and then apply the current sets of edits back to the "original" data on command.

What I've got so far is a state structure that looks roughly like this:

{
    // various assorted pieces of my app state
    entities : { TypeA, TypeB, TypeC }, // Redux-ORM generated structure based on my models
    selection : {
        selectedItem : null, // or {itemID, itemType} if something is selected
        editingEntities : {TypeA, TypeB, TypeC} // same Redux-ORM structure
    }
}

The entities key contains all the "original" data, while the selection.editingEntities key has just the empty structure until I actually start editing an item. At that time, I grab the model for that item from entities, export its contents to plain JSON, create a new session for selection.editingEntities, and reuse the same parsing logic that I use when loading a project's contents from the server. That duplicates the entire relational tree for the item that I care about into the "work-in-progress" section of my state, and then my editing form and other display components that are concerned with that item can switch to pulling their data from the selection.editingEntities section instead.

From there, my editing form is dispatching edit actions as I fiddle with my input fields, the edit actions are applied to the selection.editingEntities data, the controlled inputs are updating, and the other components displaying the "work-in-progress" state of this item are updating as well. So far so good.

The problem I'm now looking at is trying to actually apply those edits back to the "original" section of my state in the entities key. My first thought was to simply grab the corresponding model from the entities section, call itemModel.delete(), and then do the same from-scratch "load/parse" call to immediately re-insert all the data back into there. However, the delete call doesn't seem to be cascading to the relations, so when my parse logic calls, say, TypeC.create(parsedData), I get an error indicating that an instance of TypeC with that ID already exists.

I then tried looking at Model.update(), which I'm already using for trivial updates directly to the model's attributes, but I'm not quite sure how to make it work with updating all the relations as well. Looking at the source, I see that it tries to do some intelligent diffing and updating of relational fields, but I'm unclear on how to assemble an object argument that triggers that. If I were to run, say, originalItemModel.update(modifiedItemModel.ref), that would presumably have the direct relational fields (with IDs), but not the many-relational fields.

Any tips or suggestions on a viable approach to porting/applying the edits from my editingEntities section of state to my entities section of state would be appreciated. Thanks!

@tommikaikkonen

This comment has been minimized.

Show comment
Hide comment
@tommikaikkonen

tommikaikkonen Jun 2, 2016

Owner

Hmm, interesting problem. Are you creating or deleting any existing entities when editing? Or only changing items' attributes? Given that you get an error when calling TypeC.create(...), I guess you're creating some new entities. One thing you could do is use a "get or create" logic when applying your updates. For each item you want to create/update, check first if it exists, and create or update based on that. Basically a deep merge between entitities and editingEntities (and possibly some deletions) is all that's needed, or are there some edge cases?

The current default for delete is to SET_NULL in SQL terms. No option for DELETE currently. Would being able to cascade the deletes fix your problem? Then I would say the easiest way to fix this is to have that option in Redux-ORM.

The "intelligent" logic in Model.update only refers to many-to-many relationships; e.g. you set a many-to-many related field's value to [item2, item3, item4] in update, it clears the previous associated items. I don't think that's very related to your situation.

In general, Redux-ORM is modelled after relational databases and the ORM's built on top of that. By framing the problem in SQL, e.g. a similar scenario on the backend, you might be able to search for some solutions that would be directly applicable to this one. Having an editing session with items spanning multiple tables (Models) and applying those edits at once can get complicated in normal SQL databases too :)

Owner

tommikaikkonen commented Jun 2, 2016

Hmm, interesting problem. Are you creating or deleting any existing entities when editing? Or only changing items' attributes? Given that you get an error when calling TypeC.create(...), I guess you're creating some new entities. One thing you could do is use a "get or create" logic when applying your updates. For each item you want to create/update, check first if it exists, and create or update based on that. Basically a deep merge between entitities and editingEntities (and possibly some deletions) is all that's needed, or are there some edge cases?

The current default for delete is to SET_NULL in SQL terms. No option for DELETE currently. Would being able to cascade the deletes fix your problem? Then I would say the easiest way to fix this is to have that option in Redux-ORM.

The "intelligent" logic in Model.update only refers to many-to-many relationships; e.g. you set a many-to-many related field's value to [item2, item3, item4] in update, it clears the previous associated items. I don't think that's very related to your situation.

In general, Redux-ORM is modelled after relational databases and the ORM's built on top of that. By framing the problem in SQL, e.g. a similar scenario on the backend, you might be able to search for some solutions that would be directly applicable to this one. Having an editing session with items spanning multiple tables (Models) and applying those edits at once can get complicated in normal SQL databases too :)

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jun 2, 2016

Collaborator

Pinged you on Reactiflux - would like to talk this over in depth when you have some free time. That said, let me try to give a higher-level overview.

I've got three core model classes, CoreTypeA, CoreTypeB, and CoreTypeC. Instances of CoreTypeA can exist on their own, as a list of direct children under CoreTypeC, or as grandchildren of CoreTypeB with IntermediateTypeD in between. Each core type also has a one-to-one relation to DataCollectionE, which has instances of DataItemF. Finally, all three core types plus IntermediateTypeD are displayed in a treeview with checkboxes, so they all have a one-to-one relation with a CheckState model that tracks whether that tree item should have its checkbox displayed as checked, unchecked, or partially checked.

When I edit one of the core types, the general editing scenarios are:

  • Editing the direct attributes of that model instance
  • Adding, deleting, and editing instances of DataItemF in the related DataCollectionE instance
  • For CoreTypeB, adding, editing, reordering, and deleting instances of IntermediateTypeD and their child CoreTypeA instances.

And all three of those scenarios could be done during a single "edit this item" session. So, when I hit the "Apply Edits" button in my UI, I need to take the entire relational tree I've been fiddling with in my "work-in-progress" editingEntities section of state, and completely override whatever the values were in the original instance in my "current values" entities section of state.

My existing backend uses Play 1.3, which has a nice Model abstraction layer over Hibernate. For client->server CRUD updates, the approach I originally implemented was just "serialize the client model to a dumb DTO, ship to the server, delete every child of that server-side Model from the DB, and re-build based on the DTO". It's stupid code, but it kept things simple, and the app profile is fairly low-usage anyway, so it was never a scaling issue.

So, when I started looking at how to "apply" my edits from one section of state to another, I thought I might be able to do the same kind of thing. I already had written code to parse the nested JSON coming back from my server whenever I load a project (using Model.create(), and code to serialize my Redux-ORM models back to JSON, so the simplest approach to copy the data for a given item and its relations from entities to editingEntities was to serialize the item I'm editing, and re-"parse" it. Works fine when going into editingEntities, because I start with a blank slate each time. But, reversing the process and going from editingEntities to entities is the issue.

That.... may or may not have actually clarified the situation. Like I said, if you've got time to chat on Reactiflux, I'd appreciate it.

Collaborator

markerikson commented Jun 2, 2016

Pinged you on Reactiflux - would like to talk this over in depth when you have some free time. That said, let me try to give a higher-level overview.

I've got three core model classes, CoreTypeA, CoreTypeB, and CoreTypeC. Instances of CoreTypeA can exist on their own, as a list of direct children under CoreTypeC, or as grandchildren of CoreTypeB with IntermediateTypeD in between. Each core type also has a one-to-one relation to DataCollectionE, which has instances of DataItemF. Finally, all three core types plus IntermediateTypeD are displayed in a treeview with checkboxes, so they all have a one-to-one relation with a CheckState model that tracks whether that tree item should have its checkbox displayed as checked, unchecked, or partially checked.

When I edit one of the core types, the general editing scenarios are:

  • Editing the direct attributes of that model instance
  • Adding, deleting, and editing instances of DataItemF in the related DataCollectionE instance
  • For CoreTypeB, adding, editing, reordering, and deleting instances of IntermediateTypeD and their child CoreTypeA instances.

And all three of those scenarios could be done during a single "edit this item" session. So, when I hit the "Apply Edits" button in my UI, I need to take the entire relational tree I've been fiddling with in my "work-in-progress" editingEntities section of state, and completely override whatever the values were in the original instance in my "current values" entities section of state.

My existing backend uses Play 1.3, which has a nice Model abstraction layer over Hibernate. For client->server CRUD updates, the approach I originally implemented was just "serialize the client model to a dumb DTO, ship to the server, delete every child of that server-side Model from the DB, and re-build based on the DTO". It's stupid code, but it kept things simple, and the app profile is fairly low-usage anyway, so it was never a scaling issue.

So, when I started looking at how to "apply" my edits from one section of state to another, I thought I might be able to do the same kind of thing. I already had written code to parse the nested JSON coming back from my server whenever I load a project (using Model.create(), and code to serialize my Redux-ORM models back to JSON, so the simplest approach to copy the data for a given item and its relations from entities to editingEntities was to serialize the item I'm editing, and re-"parse" it. Works fine when going into editingEntities, because I start with a blank slate each time. But, reversing the process and going from editingEntities to entities is the issue.

That.... may or may not have actually clarified the situation. Like I said, if you've got time to chat on Reactiflux, I'd appreciate it.

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jun 5, 2016

Collaborator

After discussion on Reactiflux, it seems that the two main options are:

  • Have Redux-ORM implement cascaded deletes, delete the object tree I care about from my "original" data, and recreate in that section based on the contents of my "edited" data
  • Do a "smart recursive update", where each model class knows how to properly update itself from another instance (ie, "update my own attributes, update this 1:1 relation, update all items in this many relation", etc)

I'm giving option 2 a shot, and early indications are encouraging. Need to do some work to figure out how to manage create/delete/reorder changes in a nested collection / many-to-many relation, but the basic behavior of updating existing items appears to be working.

Collaborator

markerikson commented Jun 5, 2016

After discussion on Reactiflux, it seems that the two main options are:

  • Have Redux-ORM implement cascaded deletes, delete the object tree I care about from my "original" data, and recreate in that section based on the contents of my "edited" data
  • Do a "smart recursive update", where each model class knows how to properly update itself from another instance (ie, "update my own attributes, update this 1:1 relation, update all items in this many relation", etc)

I'm giving option 2 a shot, and early indications are encouraging. Need to do some work to figure out how to manage create/delete/reorder changes in a nested collection / many-to-many relation, but the basic behavior of updating existing items appears to be working.

@markerikson

This comment has been minimized.

Show comment
Hide comment
@markerikson

markerikson Jan 27, 2017

Collaborator

As an update, I've just posted Practical Redux Part 8: Form Draft Data Management, which shows how to implement the concepts discussed here.

Collaborator

markerikson commented Jan 27, 2017

As an update, I've just posted Practical Redux Part 8: Form Draft Data Management, which shows how to implement the concepts discussed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment