#

API Docs for: v3.8.1
Show: Inherited Protected Private Deprecated

# 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;
    }