Home Manual Reference Source Test

lib/attributes/attribute-collection.js

import Attribute from './attribute';
import Matcher from './matcher';

/**
Collection attributes provide basic support for one-to-many relationships.
For example, Threads in N1 have a collection of Labels or Folders.

When Collection attributes are marked as `queryable`, the RxDatabase
automatically creates a join table and maintains it as you create, save,
and delete models. When you call `persistModel`, entries are added to the
join table associating the ID of the model with the IDs of models in the collection.

Collection attributes have an additional clause builder, `contains`:

```coffee
db.findAll(Thread).where([Thread.attributes.categories.contains('inbox')])
```

This is equivalent to writing the following SQL:

```sql
SELECT `Thread`.`data` FROM `Thread`
INNER JOIN `ThreadLabel` AS `M1` ON `M1`.`id` = `Thread`.`id`
WHERE `M1`.`value` = 'inbox'
ORDER BY `Thread`.`last_message_received_timestamp` DESC
```

The value of this attribute is always an array of other model objects.

Section: Database
*/
export default class AttributeCollection extends Attribute {
  constructor({modelKey, jsonKey, itemClass, joinOnField, joinQueryableBy, queryable}) {
    super({modelKey, jsonKey, queryable});
    this.ItemClass = this.itemClass = itemClass;
    this.joinOnField = joinOnField;
    this.joinQueryableBy = joinQueryableBy || [];
  }

  toJSON(vals) {
    if (!vals) {
      return [];
    }

    if (!(vals instanceof Array)) {
      throw new Error(`AttributeCollection::toJSON: ${this.modelKey} is not an array.`);
    }

    const json = []
    for (const val of vals) {
      if (!(val instanceof this.ItemClass)) {
        throw new Error(`AttributeCollection::toJSON: Value \`${val}\` in ${this.modelKey} is not an ${this.ItemClass.name}`);
      }
      if (val.toJSON !== undefined) {
        json.push(val.toJSON());
      } else {
        json.push(val);
      }
    }
    return json;
  }

  fromJSON(json) {
    if (!json || !(json instanceof Array)) {
      return [];
    }
    const objs = [];

    for (const objJSON of json) {
      // Note: It's possible for a malformed API request to return an array
      // of null values. N1 is tolerant to this type of error, but shouldn't
      // happen on the API end.
      if (!objJSON) {
        continue;
      }

      if (this.ItemClass.prototype.fromJSON) {
        const obj = new this.ItemClass();
        // Important: if no ids are in the JSON, don't make them up
        // randomly.  This causes an object to be "different" each time it's
        // de-serialized even if it's actually the same, makes React
        // components re-render!
        obj.id = undefined;
        obj.fromJSON(objJSON);
        objs.push(obj);
      } else {
        const obj = new this.ItemClass(objJSON);
        obj.id = undefined;
        objs.push(obj);
      }
    }
    return objs;
  }

  /**
  @returns {Matcher} - Matcher for objects containing the provided value.
  */
  contains(val) {
    this._assertPresentAndQueryable('contains', val);
    return new Matcher(this, 'contains', val);
  }

  containsAny(vals) {
    this._assertPresentAndQueryable('contains', vals);
    return new Matcher(this, 'containsAny', vals);
  }
}