However, the get/set is all done via dot notation, meaning that Parse Objects have to be converted toJSON first, and then back, which can make the code longer than needed.
The alternative is to set properties directly via subclassing. (I have also tried with Proxy but VueJS already uses Proxy internally so it doesn’t work as expected)
class Monster extends Parse.Object {
constructor() {
super('Monster');
this.loadData();
}
loadData() {
const internal = ['id','className','createdAt','updatedAt', 'ACL']
const data = this.attributes;
for (const key in data) {
if (internal.includes(key)) {
continue;
}
this[key] = data[key]; // is this dangerous
}
}
async save() {
const internal = ['id','className','createdAt','updatedAt', 'ACL']
for (const key in this) {
if (internal.includes(key)) {
continue;
}
if (this.get(key) !== this[key]) {
this.set(key, this[key]);
}
}
await super.save();
}
_finishFetch(serverData) {
super._finishFetch(serverData);
this.loadData();
}
}
It might be a bit of a trivial question: but are there any risks with setting properties directly on a Parse.Object this way?
I have some thoughts, but take them with a grain of salt as I’ve never used VueJS…
One issue I can see occurring with:
this[key] = data[key]; // is this dangerous
Is this ParseObject is a class/reference type.You are probably familiar with the problems that can occur with reference types when it comes to threading, but this can also cause issues if your application is using this object in a view/onscreen or in multiple places. Basically modifications to your Monster object from anywhere will be reflected in your review even though you don’t expect them to. This may be what you want or not.
To be fair, I think most/all of the Parse SDKs suffer from depending on reference types except for the Parse-Swift SDK. These seems problematic IMO, particularly in situations when LiveQuery or GraphQL which allow objects to be modified/updated via web sockets.
If VueJS is single threaded, or 1 object is only used in one place at a time, or you protect protect against threading issues (locks, etc), I think your setup is fine, and you can disregard what I said above.
Thank you so much for the detailed reply, I really appreciate it.
VueJS allows you to bind data to UI and automatically updates UI for you reflective of the data, so I think if that’s an outcome of the subclass, it should be fine. Perhaps I should make a copy of data[key]?
The main concern I was thinking is whether this could potentially override any core properties, with unexpected impacts on other core functions.
will this not cause all the keys to be registered as dirty and therefore cause the entire model to be patched rather than just the dirty fields? It seems to from my initial test and makes sense that it would as you are re-set()ting every field in the object
why would you set props on this instance itself and not on a nested ‘this.data’ object? What are the advantages of this?
Even though I don’t know VueJS, this sounds similar to SwiftUI. Binding the view directly to the model, in your case Monster seems like it can potentially run into the issues I mentioned since model is a reference type. In SwiftUI and MVVM, there is typically a intermediary between the Model and View. It’s suggested to have the Model as value type, the View as a value type, and the ViewModel (intermediary) as reference type. It seems like you may be using the Monster model as a ViewModel when comparing to MVVM, but if you are know how to handle the problems I mentioned then you should be fine.
The main concern I was thinking is whether this could potentially override any core properties, with unexpected impacts on other core functions.
I see, I don’t think I provided anything related to these concerns
You’re correct - my code sends data even when it’s not required. I added a line on the save function that if data isn’t changed, don’t set.
And secondly, the reason I set it directly on this was because I wanted to access monster.name. I might be incorrect, but I think VueJS’ reactivity doesn’t work as expected on “deeper” properties.
Actually Vue’s reactivity WILL recognize deeply nested properties added to root object when that root object (eg monster.data) is set as reactive on a vue instance, FYI. In any case, it’s a good solution, I borrowed your _finishFetch and added this to my wrapper class. Shalom.
I should note on the master version of the JS SDK, you can register subclasses without needing to definitively state “Class Name”. This can allow you to easily make one core helper for all your classes, such as:
class ParseVueObject extends Parse.Object {
constructor(className) {
// constructor className is only available on master
super(className);
this.loadData();
}
loadData() {
const internal = ['id', 'className', 'createdAt', 'updatedAt', 'ACL'];
const data = this.attributes;
for (const key in data) {
if (internal.includes(key)) {
continue;
}
this[key] = data[key];
}
}
async save() {
const internal = ['id', 'className', 'createdAt', 'updatedAt', 'ACL'];
for (const key in this) {
if (internal.includes(key)) {
continue;
}
if (JSON.stringify(this[key]) !== JSON.stringify(this.get(key))) {
this.set(key, this[key]);
}
}
await super.save();
}
_finishFetch(serverData) {
super._finishFetch(serverData);
this.loadData();
}
}
const classNames = ['ClassOne', 'ClassTwo', 'ClassThree'];
for (const className of classNames) {
Parse.Object.registerSubclass(className, ParseVueObject);
}
It does help. I have extracted a few elements from your ideas.
Just one thought - when it comes to extending the user object, you might want to make sessionToken an internal key as well - otherwise it gets set and subsequently becomes a dirty property on the user object which gets saved if you save your user.
@dblythy Following up on this thread from the spring.
If one wanted to tap into the _finishFetch method globally (across all classes) and without having to make a new Parse.Object.extend for every class, what is the right way to do this? Calling super is not allowed in non-class definition methods so I can’t a way to do it.
Eg: the following cannot work: Parse.Object.prototype._finishFetch = function (serverData) { ... }
I’m stuck on this one … I simply don’t want to have to create a new class for all 100+ classes I have to work with just to get access to this hook.
Of course the long term and better solution would be if Parse.Object (and Parse.Query) offered hooks that you could tap into, as it down with #initialize - eg beforeRead, afterRead, beforeSave, afterSave
I wasn’t quite happy with my previous solution to this and felt like there could be a cleaner way to solve this issue.
I have created a new approach and a PR which should in theory be able to automatically traverse multiple levels of pointers, allow dot notation for every class, etc.
I use Vuejs too (mostly with Nuxtjs) so this PR looks very interesting (not sure how I missed it ). I’ve taken to not using the SDK because of all that’s mentioned here, but this could seriously change that, if it gets merged. I suspect this will help most modern frameworks!
@woutercouvaras Would you want to test out the PR and give some feedback? The reason it hasn’t been merged yet becase we want to know about potential side effects before merging.