#

API Docs for: v3.16.0-alpha.2
Show: Inherited Protected Private Deprecated

# File: ../store/addon/-private/system/references/has-many.js

    import { resolve } from 'rsvp';
    import { get } from '@ember/object';
    import Reference from './reference';
    import { DEBUG } from '@glimmer/env';
    import { assertPolymorphicType } from '@ember-data/store/-debug';
    import recordDataFor from '../record-data-for';

    /**
      @module @ember-data/store
    */

    /**
     A `HasManyReference` is a low-level API that allows users and addon
     authors to perform meta-operations on a has-many relationship.

     @class HasManyReference
     @extends Reference
     */
    export default class HasManyReference extends Reference {
      constructor(store, parentInternalModel, hasManyRelationship, key) {
        super(store, parentInternalModel);
        this.key = key;
        this.hasManyRelationship = hasManyRelationship;
        this.type = hasManyRelationship.relationshipMeta.type;
        this.parent = parentInternalModel.recordReference;
        this.parentInternalModel = parentInternalModel;

        // TODO inverse
      }

      _resource() {
        return this.recordData.getHasMany(this.key);
      }

      /**
       This returns a string that represents how the reference will be
       looked up when it is loaded. If the relationship has a link it will
       use the "link" otherwise it defaults to "id".

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```javascript
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       // get the identifier of the reference
       if (commentsRef.remoteType() === "ids") {
         let ids = commentsRef.ids();
       } else if (commentsRef.remoteType() === "link") {
         let link = commentsRef.link();
       }
       ```

       @method remoteType
       @return {String} The name of the remote type. This should either be `link` or `ids`
       */
      remoteType() {
        let value = this._resource();
        if (value && value.links && value.links.related) {
          return 'link';
        }

        return 'ids';
      }

      /**
       `ids()` returns an array of the record IDs in this relationship.

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```javascript
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       commentsRef.ids(); // ['1']
       ```

       @method ids
       @return {Array} The ids in this has-many relationship
       */
      ids() {
        let resource = this._resource();

        let ids = [];
        if (resource.data) {
          ids = resource.data.map(data => data.id);
        }

        return ids;
      }

      /**
       `push` can be used to update the data in the relationship and Ember
       Data will treat the new data as the canonical value of this
       relationship on the backend.

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       commentsRef.ids(); // ['1']

       commentsRef.push([
       [{ type: 'comment', id: 2 }],
       [{ type: 'comment', id: 3 }],
       ])

       commentsRef.ids(); // ['2', '3']
       ```

       @method push
       @param {Array|Promise} objectOrPromise a promise that resolves to a JSONAPI document object describing the new value of this relationship.
       @return {ManyArray}
       */
      push(objectOrPromise) {
        return resolve(objectOrPromise).then(payload => {
          let array = payload;

          if (typeof payload === 'object' && payload.data) {
            array = payload.data;
          }

          let internalModels = array.map(obj => {
            let record = this.store.push(obj);

            if (DEBUG) {
              let relationshipMeta = this.hasManyRelationship.relationshipMeta;
              assertPolymorphicType(this.internalModel, relationshipMeta, record._internalModel, this.store);
            }
            return recordDataFor(record);
          });

          this.hasManyRelationship.computeChanges(internalModels);

          return this.internalModel.getHasMany(this.hasManyRelationship.key);
          // TODO IGOR it seems wrong that we were returning the many array here
          //return this.hasManyRelationship.manyArray;
        });
      }

      _isLoaded() {
        let hasRelationshipDataProperty = get(this.hasManyRelationship, 'hasAnyRelationshipData');
        if (!hasRelationshipDataProperty) {
          return false;
        }

        let members = this.hasManyRelationship.members.toArray();

        //TODO Igor cleanup
        return members.every(recordData => {
          let store = this.parentInternalModel.store;
          let internalModel = store._internalModelForResource(recordData.getResourceIdentifier());
          return internalModel.isLoaded() === true;
        });
      }

      /**
       `value()` synchronously returns the current value of the has-many
       relationship. Unlike `record.get('relationshipName')`, calling
       `value()` on a reference does not trigger a fetch if the async
       relationship is not yet loaded. If the relationship is not loaded
       it will always return `null`.

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```javascript
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       post.get('comments').then(function(comments) {
         commentsRef.value() === comments
       })
       ```

       @method value
       @return {ManyArray}
       */
      value() {
        if (this._isLoaded()) {
          return this.internalModel.getManyArray(this.key);
        }

        return null;
      }

      /**
       Loads the relationship if it is not already loaded.  If the
       relationship is already loaded this method does not trigger a new
       load. This causes a request to the specified
       relationship link or reloads all items currently in the relationship.

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```javascript
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       commentsRef.load().then(function(comments) {
         //...
       });
       ```

       You may also pass in an options object whose properties will be
       fed forward. This enables you to pass `adapterOptions` into the
       request given to the adapter via the reference.

       Example

       ```javascript
       commentsRef.load({ adapterOptions: { isPrivate: true } })
         .then(function(comments) {
           //...
         });
       ```

       ```app/adapters/comment.js
       export default ApplicationAdapter.extend({
         findMany(store, type, id, snapshots) {
           // In the adapter you will have access to adapterOptions.
           let adapterOptions = snapshots[0].adapterOptions;
         }
       });
       ```

       @method load
       @param {Object} options the options to pass in.
       @return {Promise} a promise that resolves with the ManyArray in
       this has-many relationship.
       */
      load(options) {
        return this.internalModel.getHasMany(this.key, options);
      }

      /**
       Reloads this has-many relationship. This causes a request to the specified
       relationship link or reloads all items currently in the relationship.

       Example

       ```app/models/post.js
       import Model, { hasMany } from '@ember-data/model';
       export default Model.extend({
         comments: hasMany({ async: true })
       });
       ```

       ```javascript
       let post = store.push({
         data: {
           type: 'post',
           id: 1,
           relationships: {
             comments: {
               data: [{ type: 'comment', id: 1 }]
             }
           }
         }
       });

       let commentsRef = post.hasMany('comments');

       commentsRef.reload().then(function(comments) {
         //...
       });
       ```

       You may also pass in an options object whose properties will be
       fed forward. This enables you to pass `adapterOptions` into the
       request given to the adapter via the reference. A full example
       can be found in the `load` method.

       Example

       ```javascript
       commentsRef.reload({ adapterOptions: { isPrivate: true } })
       ```

       @method reload
       @param {Object} options the options to pass in.
       @return {Promise} a promise that resolves with the ManyArray in this has-many relationship.
       */
      reload(options) {
        return this.internalModel.reloadHasMany(this.key, options);
      }
    }