hybridsjs / hybrids

Extraordinary JavaScript UI framework with unique declarative and functional architecture
https://hybrids.js.org
MIT License
3.03k stars 85 forks source link

When saving a model, linked models cannot be updated #224

Closed Qsppl closed 9 months ago

Qsppl commented 9 months ago

Example: https://codepen.io/qsppl/pen/gOqqzPX?editors=1011

There is a User and Comments about him. When creating a new Comment, you need to update the User model to add this Comment to him.

image

Below I have described 4 options to solve this problem, but they all have problems. Maybe there is a way to simply reload the model from the server as is done the first time the model is requested, but do not delete the cache until new data is loaded?

async function save(host: IACommentComponent) {
  await store.submit(host.draft)
  // we clear the component if it is a spawner in order to be able to create the next model
  if (host.spawner) host.draft = null
  // the spawner will always be in edit mode
  if (!host.spawner) host.edit = false

  // ############### Updating related data ################# //

  // option 1 - store.clear: Hybrids-cache is lost and the component is unavailable until new data is loaded
  // GET /user/<id> - is ok
  store.clear(host.user)

  // option 2 - store.set: Hybrids-cache without losing, the component is available during the update
  // POST /user/update/<id> - not ok.
  // problem: the server should expect that if they send it a change request with an empty body,
  // then it should simply return the current version of the model instead of validating and saving it.
  // It's not always possible
  store.set(host.user, {})

  // option 3 - [Model, {loose: true}]: Hybrids-cache without losing, the component is available during the update
  // GET /user/<id> - ok
  // problem: Can only be applied to enumerated models.
  // problem: When the data is updated, not only the User is updated,
  // but also all associated models
  // (in my case, thousands of queries are called when creating a single comment. This is unacceptable.)

  // option 4 - store.submit(host.user, {comments: [...host.user.comments, host.comment]}): save NOT the Comment,
  // but the User with a nested Comment, and also return the User with a nested Comment.
  // This breaks HTTP caching and is very inconvenient
}
smalluban commented 9 months ago

For the sake of future readers, I assume that you have the following relation:

user -> comment[] (one to many)

Reverse relation

Regardless of how the relation is stored in the database, it's always the safest to store reverse relation in the model, then you have many to one (from many).

In this case, you would have:

const Comment = {
  user: User,
  [store.connect]: {
    ...,
    list({ user }) { 
      // get list of comments for the user
      return api.get(`/users/${user}/comments`)...
    },
  },
};

In the above example, the user model doesn't have to include that relation at all. If you need a list of comments related to the user you call const listForUser = store.get([Comment], { user: "1" }).

To prevent invalidating all of the lists (for all of the users), when creating a comment, you can clear the list for a specific user by passing the list to the store.clear(listForUser, false).

EDIT: This nested model idea was wrong, sorry (I removed it to avoid confusion) ;)

Qsppl commented 9 months ago

Thanks, store.clear(model, false) completely solved the problem.

I store pointers to related models and in both sides:

  1. The user has links to comments.
  2. Comments have links to the user about whom the comment was created.

image

It is necessary to store pointers to comments with the user (1) in order to have information about user comments before loading the comments themselves. This is especially important when lazy loading comments.

Here we see that the user/company has comments: image

But here it’s not: image

In this case, comments will not load until the user opens the corresponding comments section

With this approach, hybrids loads each model separately, but in my case this is even better due to the use of http2 and http caching. In this option, when changing one comment, there is no need to invalidate all user comments, and there is also no need to write complex logic for updating the cache when adding a new comment.