API Docs for: v3.4.3
Show:

File: 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/-debug';

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

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

  /**
     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
     export default DS.Model.extend({
       comments: DS.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() {
    if (this.hasManyRelationship.link) {
      return 'link';
    }

    return 'ids';
  }

  /**
     The link Ember Data will use to fetch or reload this has-many
     relationship.

     Example

     ```app/models/post.js
     export default DS.Model.extend({
       comments: DS.hasMany({ async: true })
     });
     ```

     ```javascript
     let post = store.push({
       data: {
         type: 'post',
         id: 1,
         relationships: {
           comments: {
             links: {
               related: '/posts/1/comments'
             }
           }
         }
       }
     });

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

     commentsRef.link(); // '/posts/1/comments'
     ```

     @method link
     @return {String} The link Ember Data will use to fetch or reload this has-many relationship.
  */
  link() {
    return this.hasManyRelationship.link;
  }

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

     Example

     ```app/models/post.js
     export default DS.Model.extend({
       comments: DS.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 members = this.hasManyRelationship.members.toArray();

    return members.map(function(internalModel) {
      return internalModel.id;
    });
  }

  /**
     The metadata for the has-many relationship.

     Example

     ```app/models/post.js
     export default DS.Model.extend({
       comments: DS.hasMany({ async: true })
     });
     ```

     ```javascript
     let post = store.push({
       data: {
         type: 'post',
         id: 1,
         relationships: {
           comments: {
             links: {
               related: {
                 href: '/posts/1/comments',
                 meta: {
                   count: 10
                 }
               }
             }
           }
         }
       }
     });

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

     commentsRef.meta(); // { count: 10 }
     ```

     @method meta
     @return {Object} The meta information for the has-many relationship.
  */
  meta() {
    return this.hasManyRelationship.meta;
  }

  /**
     `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
     export default DS.Model.extend({
       comments: DS.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 {DS.ManyArray}
  */
  push(objectOrPromise) {
    return resolve(objectOrPromise).then(payload => {
      let array = payload;

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

      let internalModels;
      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 record._internalModel;
      });

      this.hasManyRelationship.computeChanges(internalModels);

      return this.hasManyRelationship.manyArray;
    });
  }

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

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

    return members.every(function(internalModel) {
      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
     export default DS.Model.extend({
       comments: DS.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 {DS.ManyArray}
  */
  value() {
    if (this._isLoaded()) {
      return this.hasManyRelationship.manyArray;
    }

    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.

     Example

     ```app/models/post.js
     export default DS.Model.extend({
       comments: DS.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) {
    // TODO this can be simplified
    if (!this._isLoaded()) {
      return this.hasManyRelationship.getData(options);
    }

    return resolve(this.hasManyRelationship.manyArray);
  }

  /**
     Reloads this has-many relationship.

     Example

     ```app/models/post.js
     export default DS.Model.extend({
       comments: DS.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.hasManyRelationship.reload(options);
  }
}