Skip to content

setInstanceProperty fails comparison on objects or custom types with objects #517

Closed

Description

@kapouer

Hi,
the comparison if (opts.data[key] !== value) { at https://github.com/dresende/node-orm2/blob/38e76722/lib/Instance.js#L453 will never be true when modifying a property of a column that is an object. The horrible workaround is:

var props = source.prop;
props.file = filename;
source.prop = null; // or anything that is not props
source.prop = props;

i suggest to improve the comparison when the value is an object:

if (opts.data[key] !== value ||
    (typeof value == "object" &&
     propertyToValue(value) !== propertyToValue(opts.data[key])
    )
   ) {../..}

so a thorough comparison is done in case of equality of objects.

Activity

  1. dxg commented on Jun 4, 2014

    @dxg
    Collaborator

    This problem is harder to solve than it seems.
    I tried the above as well as a few other things, and nothing worked.

    var Place = db.define('place', {
      position: { type: 'object' }
    });
    
    ...
    
    Place.create({ position: { x: 2, y: 3 } }, ...);
    
    Place.get(1, function (err, item) {
      console.log(item.saved()); // "true"
      item.position.x = 5;
      console.log(item.saved()); // "true"
    })

    .save() will only persist to the database if saved() returns false.

    Unfortunately, in the above example when we set x to 5, the code on line 453 isn't called.
    And even if it was (eg, by doing p = item.position; item.position = null; item.position = p) the comparison still wouldn't return true because value points to the same object in memory as opts.data[key] so we'd be comparing it to itself.

    The solutions I can see are:

    1. Always assume object properties have changed
    2. When saving, fetch all Object properties from the database and compare to what we have
    3. Keep a shadow copy of all instance data and do a comparison when calling save()
    4. Allow something like item.save({ force: true }, ...) and maybe a global setting for this
    5. Add item.markAsDirty('position')

    Number 1 goes against the principle of least surprise.
    Number 2 would result in extra queries, negatively impacting speed.
    Number 3 would double memory usage - problematic if a property is storing a Buffer() of a large file.
    Number 4 & 5 is rather manual.

    I'm leaning towards number 4 and/or 5 but it's not exactly a nice solution..

  2. kapouer commented on Jun 4, 2014

    @kapouer
    ContributorAuthor
    1. item.set('position.x', 5)
      (or variants)
  3. dxg commented on Jun 4, 2014

    @dxg
    Collaborator

    That sounds better than my ideas.

  4. added a commit that references this issue on Jun 5, 2014
  5. dxg commented on Jun 5, 2014

    @dxg
    Collaborator

    We now have:

    item.set('position.x', 2)
    item.set(['position','x'], 2) // in case you have objects like { "a.b" : 4  }
    item.markAsDirty('position')

    set() will only mark the object as dirty if you are changing the value. If x was 2 and you set it to 2, it won't affect dirty state.

    Published as version 2.1.15.

  6. kapouer commented on Jun 5, 2014

    @kapouer
    ContributorAuthor

    that's wonderful, thank you

  7. kapouer commented on Sep 10, 2014

    @kapouer
    ContributorAuthor

    The only missing thing now is a mention of that in the docs...

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions