API Docs for: v3.6.0
Show:

File: addon/-private/system/relationships/state/has-many.js

import { assertPolymorphicType } from 'ember-data/-debug';
import Relationship from './relationship';
import OrderedSet from '../../ordered-set';
import { isNone } from '@ember/utils';

export default class ManyRelationship extends Relationship {
  constructor(store, inverseKey, relationshipMeta, recordData, inverseIsAsync) {
    super(store, inverseKey, relationshipMeta, recordData, inverseIsAsync);
    this.canonicalState = [];
    this.currentState = [];
    this._willUpdateManyArray = false;
    this._pendingManyArrayUpdates = null;
  }

  removeInverseRelationships() {
    super.removeInverseRelationships();

    /* TODO Igor make sure this is still working
    if (this._promiseProxy) {
      this._promiseProxy.destroy();
    }
    */
  }

  addCanonicalRecordData(recordData, idx) {
    if (this.canonicalMembers.has(recordData)) {
      return;
    }
    if (idx !== undefined) {
      this.canonicalState.splice(idx, 0, recordData);
    } else {
      this.canonicalState.push(recordData);
    }
    super.addCanonicalRecordData(recordData, idx);
  }

  inverseDidDematerialize(inverseRecordData) {
    super.inverseDidDematerialize(inverseRecordData);
    if (this.isAsync) {
      this.notifyManyArrayIsStale();
    }
  }

  addRecordData(recordData, idx) {
    if (this.members.has(recordData)) {
      return;
    }

    assertPolymorphicType(this.recordData, this.relationshipMeta, recordData, this.store);
    super.addRecordData(recordData, idx);
    // make lazy later
    if (idx === undefined) {
      idx = this.currentState.length;
    }
    this.currentState.splice(idx, 0, recordData);
    // TODO Igor consider making direct to remove the indirection
    // We are not lazily accessing the manyArray here because the change is coming from app side
    // this.manyArray.flushCanonical(this.currentState);
    this.notifyHasManyChange();
  }

  removeCanonicalRecordDataFromOwn(recordData, idx) {
    let i = idx;
    if (!this.canonicalMembers.has(recordData)) {
      return;
    }
    if (i === undefined) {
      i = this.canonicalState.indexOf(recordData);
    }
    if (i > -1) {
      this.canonicalState.splice(i, 1);
    }
    super.removeCanonicalRecordDataFromOwn(recordData, idx);
    //TODO(Igor) Figure out what to do here
  }

  removeAllCanonicalRecordDatasFromOwn() {
    super.removeAllCanonicalRecordDatasFromOwn();
    this.canonicalMembers.clear();
    this.canonicalState.splice(0, this.canonicalState.length);
    super.removeAllCanonicalRecordDatasFromOwn();
  }

  //TODO(Igor) DO WE NEED THIS?
  removeCompletelyFromOwn(recordData) {
    super.removeCompletelyFromOwn(recordData);

    // TODO SkEPTICAL
    const canonicalIndex = this.canonicalState.indexOf(recordData);

    if (canonicalIndex !== -1) {
      this.canonicalState.splice(canonicalIndex, 1);
    }

    this.removeRecordDataFromOwn(recordData);
  }

  flushCanonical() {
    let toSet = this.canonicalState;

    //a hack for not removing new records
    //TODO remove once we have proper diffing
    let newRecordDatas = this.currentState.filter(
      // only add new internalModels which are not yet in the canonical state of this
      // relationship (a new internalModel can be in the canonical state if it has
      // been 'acknowleged' to be in the relationship via a store.push)

      //TODO Igor deal with this
      recordData => recordData.isNew() && toSet.indexOf(recordData) === -1
    );
    toSet = toSet.concat(newRecordDatas);

    /*
    if (this._manyArray) {
      this._manyArray.flushCanonical(toSet);
    }
    */
    this.currentState = toSet;
    super.flushCanonical();
    // Once we clean up all the flushing, we will be left with at least the notifying part
    this.notifyHasManyChange();
  }

  //TODO(Igor) idx not used currently, fix
  removeRecordDataFromOwn(recordData, idx) {
    super.removeRecordDataFromOwn(recordData, idx);
    let index = idx || this.currentState.indexOf(recordData);

    //TODO IGOR DAVID INVESTIGATE
    if (index === -1) {
      return;
    }
    this.currentState.splice(index, 1);
    // TODO Igor consider making direct to remove the indirection
    // We are not lazily accessing the manyArray here because the change is coming from app side
    this.notifyHasManyChange();
    // this.manyArray.flushCanonical(this.currentState);
  }

  notifyRecordRelationshipAdded() {
    this.notifyHasManyChange();
  }

  computeChanges(recordDatas = []) {
    let members = this.canonicalMembers;
    let recordDatasToRemove = [];
    let recordDatasSet = setForArray(recordDatas);

    members.forEach(member => {
      if (recordDatasSet.has(member)) {
        return;
      }

      recordDatasToRemove.push(member);
    });

    this.removeCanonicalRecordDatas(recordDatasToRemove);

    for (let i = 0, l = recordDatas.length; i < l; i++) {
      let recordData = recordDatas[i];
      this.removeCanonicalRecordData(recordData);
      this.addCanonicalRecordData(recordData, i);
    }
  }

  setInitialRecordDatas(recordDatas) {
    if (Array.isArray(recordDatas) === false || recordDatas.length === 0) {
      return;
    }

    for (let i = 0; i < recordDatas.length; i++) {
      let recordData = recordDatas[i];
      if (this.canonicalMembers.has(recordData)) {
        continue;
      }

      this.canonicalMembers.add(recordData);
      this.members.add(recordData);
      this.setupInverseRelationship(recordData);
    }

    this.canonicalState = this.canonicalMembers.toArray();
  }

  /*
    This is essentially a "sync" version of
      notifyHasManyChange. We should work to unify
      these worlds

      - @runspired
  */
  notifyManyArrayIsStale() {
    let recordData = this.recordData;
    let storeWrapper = recordData.storeWrapper;
    storeWrapper.notifyPropertyChange(
      recordData.modelName,
      recordData.id,
      recordData.clientId,
      this.key
    );
  }

  notifyHasManyChange() {
    let recordData = this.recordData;
    let storeWrapper = recordData.storeWrapper;
    storeWrapper.notifyHasManyChange(
      recordData.modelName,
      recordData.id,
      recordData.clientId,
      this.key
    );
  }

  getData() {
    let payload = {};
    if (this.hasAnyRelationshipData) {
      payload.data = this.currentState.map(recordData => recordData.getResourceIdentifier());
    }
    if (this.link) {
      payload.links = {
        related: this.link,
      };
    }
    if (this.meta) {
      payload.meta = this.meta;
    }

    // TODO @runspired: the @igor refactor is too limiting for relationship state
    //   we should reconsider where we fetch from.
    payload._relationship = this;

    return payload;
  }

  updateData(data, initial) {
    let recordDatas;
    if (isNone(data)) {
      recordDatas = undefined;
    } else {
      recordDatas = new Array(data.length);
      for (let i = 0; i < data.length; i++) {
        recordDatas[i] = this.recordData.storeWrapper.recordDataFor(data[i].type, data[i].id);
      }
    }
    if (initial) {
      this.setInitialRecordDatas(recordDatas);
    } else {
      this.updateRecordDatasFromAdapter(recordDatas);
    }
  }

  /**
   * Flag indicating whether all inverse records are available
   *
   * true if inverse records exist and are all loaded (all not empty)
   * true if there are no inverse records
   * false if the inverse records exist and any are not loaded (any empty)
   *
   * @return {boolean}
   */
  get allInverseRecordsAreLoaded() {
    // check currentState for unloaded records
    let hasEmptyRecords = this.currentState.reduce((hasEmptyModel, i) => {
      return hasEmptyModel || i.isEmpty();
    }, false);
    // check un-synced state for unloaded records
    if (!hasEmptyRecords && this.willSync) {
      hasEmptyRecords = this.canonicalState.reduce((hasEmptyModel, i) => {
        return hasEmptyModel || !i.isEmpty();
      }, false);
    }

    return !hasEmptyRecords;
  }
}

function setForArray(array) {
  var set = new OrderedSet();

  if (array) {
    for (var i = 0, l = array.length; i < l; i++) {
      set.add(array[i]);
    }
  }

  return set;
}