javascriptjsdata

Proper way of updating js-data relation objects


I have a linked list of objects of the same type:

GET /cards.json

[{
  "id": 1,
  "lesserCardId": null,
  "greaterCardId": 2,
 }, {
  "id": 2,
  "lesserCardId": 1,
  "greaterCardId": 3
}, {
  "id": 3,
  "lesserCardId": 2,
  "greaterCardId": null
}]

Resource definition:

DS.defineResource({
  name: 'card',
  relations: {
    belongsTo: {
      card: [{
        localField: 'lesserCard',
        localKey: 'lesserCardId'
      }, {
        localField: 'greaterCard',
        localKey: 'greaterCardId'
      }]
    }
  }
});

js-data correctly reads json and creates object hierarchy. What I want next is to automatically update linked card properties like this:

var card1 = DS.get('card', 1);
var card4 = DS.inject('card', {'id': 4});
card1.greaterCard = card4;

// corresponding liked object properties should be updated automatically
expect(card4.lesserCard).toBe(card1);
expect(card2.lesserCard).toBeUndefined();

I have achieved this without js-data making custom setter like this:

Object.defineProperty(Card.prototype, 'greaterCard', {
  set: function (card) {
     // custom logic for updating linked list structure
     if (card === this.$greaterCard) {
       return;
     }
     this.$greaterCard.lesserCard = this.$lesserCard;
     this.$greaterCard = card;
     if (card) {
       card.lesserCard = this;
     }
     // updating depending properties
     this.$updateCardLevel();
     this.$updateCardLevelMax();
  },
  get: function () {
    return this.$greaterCard;
  }
});

But js-data introduces it's own property descriptors for relation objects. So this approach cannot be applied. I haven't found a way to hook into js-data relation property setters. I could make a separate service with method like cardService.setGreaterCard(card, greaterCard); which would update list structure, but this wouldn't be so convenient. Is there better way for updating linked objects on property change?

js-data version: 2.9.0


Solution

  • In JSData 2.x you would do this:

    var Card = DS.defineResource({
      name: 'card',
      relations: {
        belongsTo: {
          card: [
            {
              localField: 'lesserCard',
              localKey: 'lesserCardId',
              link: false
            },
            {
              localField: 'greaterCard',
              localKey: 'greaterCardId',
              link: false
            }
          ]
        }
      }
    });
    
    Object.defineProperties(Card[Card.class].prototype, {
      lesserCard: {
        set: function (lesserCard) {
           this.lesserCardId = lesserCard.id;
           lesserCard.greaterCardId = this.id;
        },
        get: function () {
          return Card.get(this.lesserCardId);
        }
      },
      greaterCard: {
        set: function (greaterCard) {
           this.greaterCardId = greaterCard.id;
           greaterCard.lesserCardId = this.id;
        },
        get: function () {
          return Card.get(this.greaterCardId);
        }
      }
    });
    

    In JSData 3.x you would just do this:

    store.defineMapper('card', {
      relations: {
        belongsTo: {
          card: [
            {
              localField: 'lesserCard',
              foreignKey: 'lesserCardId',
              set: function (Relation, card, lesserCard, originalSet) {
                lesserCard.greaterCardId = card.id;
                originalSet(lesserCard);
              }
            },
            {
              localField: 'greaterCard',
              foreignKey: 'greaterCardId',
              set: function (Relation, card, greaterCard, originalSet) {
                greaterCard.lesserCardId = card.id;
                originalSet(lesserCard);
              }
            }
          ]
        }
      }
    });