rawmodel/framework

Saving only changed and valid fields

xpepermint opened this issue · 5 comments

by @michaelsorich, at xpepermint/vue-rawmodel#4

I will eventually have a rather large model and I intend to regularly autosave the model via a rest api. I intend to send only the fields and values that have changed since the last save which typically will only be a small fraction of the model (I could send a serialized version of the complete model and let the server sort it out, but I am hoping that I can do so on the client and only send the pertinent changes).

The presence of one or more fields that are currently invalid will not prevent saving. Currently I am intending that fields that are invalid will not be sent to the server for saving, but other valid changed fields will be sent (even though some fields are invalid).

Thus, “if (m.isChanged() && m.isValid())” isn’t quite what I wish as it will only be true if all fields are valid. As long as there is at least one field that is both changed and valid then the result should be true (i.e. there is data that can be sent to the server as a partial update/PATCH). I am happy to implement this as a new method on extended model and field classes so this is not too much of an issue if I can work out how to properly extend Model and Field.

In vue-contextable, the construction of the Model instance is not explicit. For example, I cannot see something like ‘let m = Model(…)’ - if so it would be trivial to substitute my extended version of the ReactiveModel.

Based on the example, my understanding of the standard setup is:

  • A contextable context object is made (const context = new Context())

  • Then a schema is passed in which appears to create a contextable.Model instance (e.g. context.defineModel('User', new Schema(…)) which is then stored in the context object. It would not seem easy to replace Model with an extended version at this stage without rewriting context.defineModel and createModel.

It is possible to access the Model from the context but not to set the model which seems to make it difficult to replace it with an extended version at this stage (e.g. class Modelv2 extends context[‘Lease’]{…}; context[‘Lease’] = Modelv2).

  • I presume that somewhere in the initialisation process (not sure where) the defineReactiveModel function from vue-contextable is called which retrieves the model from the context, extends it (to become a ReactiveModel), and then stores it in the vm variable. It is not clear to me how I would override the ReactiveModel definition (which is itself embedded in a defineReactiveModel function) in order to replace it with an extended version which would be called by the appropriate function in the course of initialization.

There is likely a much simpler way of extending the functionality of ReactiveModel and Field but I am not seeing it at the moment. Any hints would be appreciated.

I will eventually have a rather large model and I intend to regularly autosave the model via a rest api. I intend to send only the fields and values that have changed since the last save which typically will only be a small fraction of the model (I could send a serialized version of the complete model and let the server sort it out, but I am hoping that I can do so on the client and only send the pertinent changes).

That's something that contextable should include by default. It's a useful feature so let's add it. This is what I propose:

// model schema example
new Schema({
  instanceMethods: {
    async autoSave () {
      let data = this.toObject({
        onlyChanged: true,
        onlyValid: true
      });
      // post data to the server
    }
  }
})

In vue-contextable, the construction of the ReactiveModel instance is not explicit. For example, I cannot see something like ‘let m = ReactiveModel(…)’ - if so it would be trivial to substitute my extended version of the ReactiveModel ...

I agree and I'll see what I can do about that.

A quick follow up ... I started upgrading the underlying objectschema package. I hope I'll finish it by tomorrow. After this upgrade a new filter method will be available thus you will be able to extract an object with keys that pass the test:

model.serialize() // -> convert to Object
model.filter((field) => field.isChanged() && field.isValid()) // -> convert to Object with changed and valid keys

The filter method looks good. I would be interested in your thoughts about how best to link this to commit - basically the object with valid changes represents the changes that are valid since the last patch to the server database. Thus, simultaneously (or as close as possible) I would like the fields that are returned from filter (i.e. valid and changed) are also commited so that they are not returned by a repeat call to filter (unless there are subsequent changes). As the save may be taking place as a user is still modifying the form (i.e. the save would preferably not block user working on the form) it would be good for the commit and extraction of changed-valid fields to occur together.

This feature took a while because I had to rethink the whole concept. The main question was how to deal with nested fields. The latest package v3.0.0 is now easily extendable. It's also built on top of the latest ObjectSchema.js which provides new methods like flatten(), filter(), collect() and scroll().

Partial save/commit should now be easily achievable like this:

await model.validate({quiet: true});
...
let patch = model.filter(({path, field}) => {
  if (field.isChanged() && field.isValid()) { // if field has been changed and is valid
    field.commit(); // commit field value
    return true; // add it to the result object
  }
}); // -> serialized object with only valid fields

This is something you should pack into a class or instance method of a model.

I will eventually have a rather large model and I intend to regularly autosave the model via a rest api.

I would suggest that you execute the autosave action on blur event or make a debounced POST request to the server.

I am happy to implement this as a new method on extended model and field classes so this is not too much of an issue if I can work out how to properly extend Model and Field.

Use Schema mixins to extend a model. Note also that a Model is actually a pure javascript class/function thus you can simply do Admin extends User {}.

Please reopen this issue if this doesn't fully work for you.