4

Not sure if its a bug or a misunderstanding of how things are meant to work:

  • disable single instance mode
  • Create a new parse object (or query one from the server) and populate properties
  • Make a copy of the object (e.g. if you were editing the properties on a form and wanted the option to save/discard changes)
  • Copy is empty ....

using parse-js-sdk 1.9.2 and angular 1.4.8

  //comment out the following line and it works ok
  Parse.Object.disableSingleInstance();

  var parseObjClass = Parse.Object.extend('Comments', {
            // Instance methods
            initialize: function (attrs, options) {}
        },
        {});

        ['name', 'desc'].forEach(function (item) {

        Object.defineProperty(parseObjClass.prototype, item, {
            get: function () {
                return this.get(item);
            },
            set: function (aValue) {
                return this.set(item, aValue);
            }
        });
    });

  var parseObj = new parseObjClass();
    parseObj.name = 'Hello World'
    parseObj.desc = 'Test Object'

  console.log('original is ' + JSON.stringify(parseObj)); 
  //--> original is {"name":"Hello World","desc":"Test Object"}

  var objCopy = angular.copy(parseObj);

  console.log('copy is ' + JSON.stringify(objCopy));
  //--> copy is {}

Here is a plunker illustrating https://plnkr.co/edit/UjWe9dl6D6Qkx9ihBSMC

background : we are using disableSingleInstance mode because we have a mobile app and a a web app that can interact with the same database rows (timesheet data) and were having problems with the js-sdk not always having the latest version of the data...

simonberry
  • 910
  • 9
  • 20
  • your `parseObj` doesn't contain `name` or `desc` while logging original.. not sure how `Parse` object works though – tanmay Apr 24 '17 at 07:40
  • updated code snippet to include the full `parseObjClass` code as per the plunker – simonberry Apr 24 '17 at 08:04
  • Disabling Single Instance means you'll be able to have separate instances of objects that are supposedly the same object, and updating one will *not* update the other. I feel like for your problem, you'd prefer to keep Single Instance enabled. This doesn't mean there can only be one reference across your whole app. It means that on that individual server, any reference to a ParseObject of that class with that Id will have the same data as it is updated. – Jake T. Apr 26 '17 at 20:53
  • I ran into a lot of not having the latest data issues due to a misunderstanding of how `fetchIfNeeded` works. I had thought it would fetch data if there was new data on the server. However, it actually only fetches if you have a shell of an object (i.e. pointer field on an object returned by a query, and you didn't call include.field), that hasn't been fetched at all. Once an object is fetched at all, fetchIfNeeded will never fetch the object again (that specific instance, not a new instance with the same id). – Jake T. Apr 26 '17 at 20:54
  • As for your particular issue with your code, I imagine that Parse Objects aren't compatible with this Angular method. Write your own function that creates a new Parse Object with the same className as the one you want to copy, and iterates all of its keys to copy its data, if you'd like a separate instance that is a "copy". You may need to write a separate function for each of your Parse Classes to make sure each key is properly copied over. – Jake T. Apr 26 '17 at 20:56
  • @JakeT. Thanks for the feedback...My understanding of how Single Instance works is based largely on this conversation : https://github.com/parse-community/Parse-SDK-JS/issues/310#issuecomment-232444352 ... not sure if the is official documentation anywhere else ? We have definitely seen cases of two different clients having different data, despite multiple new queries to the server when single instance mode is not disabled. – simonberry Apr 27 '17 at 11:46
  • Also, not sure about the explanation about incompatibility with angular.copy. Why would it world perfectly if Single Instance mode is on, but not when its off ? – simonberry Apr 27 '17 at 11:49
  • I wasn't aware angular.copy worked with Single Instance mode turned off. There is clearly something incompatible between angular.copy and Parse.Objects with it turned on, though. And the problem is that Single Instance Mode only pertains to an individual client, not all clients. So, say you reference the same object multiple times on one client, each of those instances will be the same instance. But, if another user is also referencing that object several times, their own references will be a single instance, but it will be a separate instance. – Jake T. Apr 27 '17 at 18:55
  • You need to handle these issues by fetching the object and validating it's data before doing important operations that may mess things up, and utilizing beforeSave triggers on your cloud code to see if maybe the data already on an object is incompatible with what is being sent. Check out this answer to see how to compare new and old data: http://stackoverflow.com/a/25082782/4544328 How you validate this data is entirely up to you and your specific use case, so I can't really give many pointers there. – Jake T. Apr 27 '17 at 18:58
  • Also, consider opening an Issue on Parse-Server's JS SDK about the incompatibility of angular.copy when using / not using single instance. Updates come pretty slow, and they'd prefer you fix the issue yourself, but it'll at least be documented so other people running into the issue can find that, or maybe you're missing something silly and they'll point out what. – Jake T. Apr 27 '17 at 18:59

1 Answers1

3

According to angular.copy(source, [destination]); docs:

Only enumerable properties are taken into account. Non-enumerable properties (both on source and on destination) will be ignored.

And according to MDN docs for Object.defineProperty(obj, prop, descriptor):

When defining properties like that by default, it's not enumerable. Therefore you have to specify enumerable: true on the descriptor.

For example:

Object.defineProperty(parseObjClass.prototype, item, {
    enumerable: true,
    get: function () {
        return this.get(item);
    },
    set: function (aValue) {
        return this.set(item, aValue);
    }
});

UPDATE

Just realised that there are a problem with your setter and the getter the way you are using it are preventing your property of being an enumerable. You can remove the get and set and replace with a writable and it should be fine.

Object.defineProperty(parseObjClass.prototype, item, {
    enumerable: true,
    writable: true
});

If you need to use get and set you have to use a private var or something to intermediate, like get: () => _props[item];

lenilsondc
  • 9,590
  • 2
  • 25
  • 40
  • this answer looked very promising, but alas after adding `enumerable : true` it still doesn't work : [plunker](https://plnkr.co/edit/UjWe9dl6D6Qkx9ihBSMC) – simonberry Apr 29 '17 at 07:43
  • Again, I was excited with the proposed solution, but for some reason if i set `writeable:true` to the prototype definition, I can't even assign the object property in the first place (i.e. the first console.log shows an object value of {}) – simonberry May 02 '17 at 14:37