couchbaselabs / node-ottoman

Node.js ODM for Couchbase
https://ottomanjs.com/
Apache License 2.0
287 stars 98 forks source link

_depopulate does not work if keyId defined for a model #742

Closed kirstydarragh closed 7 months ago

kirstydarragh commented 9 months ago

The _depopulate() method is failing if the model has a keyId defined to override the default. This is causing other issues such as documents being persisted to the database with the object instead of id reference. E.g. In test test/model-schema-integration.spec.ts, if you change the referenced models to have a different id to the default name of "id", the test 'Ensure document save references instead of populated objects' fails:

test.only('Ensure document save references instead of populated objects', async () => {
    // const Card = model('Card', CardSchema); // ORIGINAL,
    const Card = model('Card', CardSchema, { idKey: 'cardId' }); // USING DIFFERENT ID,
    // const Cat = model('Cat', CatSchema); // ORIGINAL
    const Cat = model('Cat', CatSchema, { idKey: 'catId' }); // USING DIFFERENT ID
    const UserSchema = new Schema({
      isActive: Boolean,
      name: String,
      card: { type: CardSchema, ref: 'Card' },
      cats: [{ type: CatSchema, ref: 'Cat' }],
    });
    UserSchema.pre('save', (doc) => {
      expect(typeof doc.card).toBe('string');
      expect(typeof doc.cats[0]).toBe('string');
    });
    const User = model('User', UserSchema);

    await startInTest(getDefaultInstance());
    const cardCreated = await Card.create(cardInfo);
    const catCreated = await Cat.create({ name: 'Figaro', age: 6 });
    const catCreated2 = await Cat.create({ name: 'Garfield', age: 27 });
    console.log(`created`, cardCreated, catCreated, catCreated2);
    const user = new User(populateDoc);
    // user.card = cardCreated.id; // ORIGINAL
    user.card = cardCreated.cardId; // USING DIFFERENT ID
    // user.cats = [catCreated.id, catCreated2.id]; // ORIGINAL
    user.cats = [catCreated.catId, catCreated2.catId]; // USING DIFFERENT ID
    const validated = await user._validate();
    expect(validated).toBeTruthy();
    expect(user._getIdField()).toBe('id');
    await user.save();
    const result = await User.findById(user.id);
    await result._populate();
    expect(result.card.cardNumber).toBe(cardInfo.cardNumber);
    expect(result.cats[0].name).toBe('Figaro');
    await result.save();
    expect(typeof result.card).toBe('string');
    expect(typeof result.cats[0]).toBe('string');
    console.log(`FINAL RESULT IS`, result);
  });

Have updated the file src/model/document.ts _depopulate method:

  _depopulate(fieldsName: string | string[]) {
    let fieldsToPopulate;
    if (fieldsName) {
      fieldsToPopulate = extractSchemaReferencesFromGivenFields(fieldsName, this.$.schema);
    } else {
      fieldsToPopulate = extractSchemaReferencesFields(this.$.schema);
    }
    for (const fieldName in fieldsToPopulate) {
      const data = this[fieldName];
      if (Array.isArray(data)) {
        for (let i = 0; i < data.length; i++) {
          const field = data[i];
          if (field && field._getId && field._getId()) {
            data[i] = field._getId();
          }
        }
      } else if (typeof data === 'object') {
        if (data && data._getId && data._getId()) {
          this[fieldName] = data._getId();
        }
      }
    }
    return this;
  }

And tested it for both original 'id' and the 'catId' and 'cardId' and seems to work. . ??