/** @module ember-data */ import Evented from '@ember/object/evented'; import ArrayProxy from '@ember/array/proxy'; import { set, get, computed } from '@ember/object'; import { Promise } from 'rsvp'; import { PromiseArray } from "../promise-proxies"; import SnapshotRecordArray from "../snapshot-record-array"; /** A record array is an array that contains records of a certain modelName. The record array materializes records as needed when they are retrieved for the first time. You should not create record arrays yourself. Instead, an instance of `DS.RecordArray` or its subclasses will be returned by your application's store in response to queries. @class RecordArray @namespace DS @extends Ember.ArrayProxy @uses Ember.Evented */ export default ArrayProxy.extend(Evented, { init() { this._super(...arguments); /** The array of client ids backing the record array. When a record is requested from the record array, the record for the client id at the same index is materialized, if necessary, by the store. @property content @private @type Ember.Array */ this.set('content', this.content || null); /** The flag to signal a `RecordArray` is finished loading data. Example ```javascript var people = store.peekAll('person'); people.get('isLoaded'); // true ``` @property isLoaded @type Boolean */ this.isLoaded = this.isLoaded || false; /** The flag to signal a `RecordArray` is currently loading data. Example ```javascript var people = store.peekAll('person'); people.get('isUpdating'); // false people.update(); people.get('isUpdating'); // true ``` @property isUpdating @type Boolean */ this.isUpdating = false; /** The store that created this record array. @property store @private @type DS.Store */ this.store = this.store || null; this._updatingPromise = null; }, replace() { throw new Error(`The result of a server query (for all ${this.modelName} types) is immutable. To modify contents, use toArray()`); }, /** The modelClass represented by this record array. @property type @type DS.Model */ type: computed('modelName', function() { if (!this.modelName) { return null; } return this.store._modelFor(this.modelName); }).readOnly(), /** Retrieves an object from the content by index. @method objectAtContent @private @param {Number} index @return {DS.Model} record */ objectAtContent(index) { let internalModel = get(this, 'content').objectAt(index); return internalModel && internalModel.getRecord(); }, /** Used to get the latest version of all of the records in this array from the adapter. Example ```javascript var people = store.peekAll('person'); people.get('isUpdating'); // false people.update().then(function() { people.get('isUpdating'); // false }); people.get('isUpdating'); // true ``` @method update */ update() { if (get(this, 'isUpdating')) { return this._updatingPromise; } this.set('isUpdating', true); let updatingPromise = this._update().finally(() => { this._updatingPromise = null; if (this.get('isDestroying') || this.get('isDestroyed')) { return } this.set('isUpdating', false); }); this._updatingPromise = updatingPromise; return updatingPromise; }, /* Update this RecordArray and return a promise which resolves once the update is finished. */ _update() { return this.store.findAll(this.modelName, { reload: true }); }, /** Adds an internal model to the `RecordArray` without duplicates @method _pushInternalModels @private @param {InternalModel} internalModel */ _pushInternalModels(internalModels) { // pushObjects because the internalModels._recordArrays set was already // consulted for inclusion, so addObject and its on .contains call is not // required. get(this, 'content').pushObjects(internalModels); }, /** Removes an internalModel to the `RecordArray`. @method removeInternalModel @private @param {InternalModel} internalModel */ _removeInternalModels(internalModels) { get(this, 'content').removeObjects(internalModels); }, /** Saves all of the records in the `RecordArray`. Example ```javascript var messages = store.peekAll('message'); messages.forEach(function(message) { message.set('hasBeenSeen', true); }); messages.save(); ``` @method save @return {DS.PromiseArray} promise */ save() { let promiseLabel = `DS: RecordArray#save ${this.modelName}`; let promise = Promise.all(this.invoke('save'), promiseLabel) .then(() => this, null, 'DS: RecordArray#save return RecordArray'); return PromiseArray.create({ promise }); }, _dissociateFromOwnRecords() { this.get('content').forEach(internalModel => { let recordArrays = internalModel.__recordArrays; if (recordArrays) { recordArrays.delete(this); } }); }, /** @method _unregisterFromManager @private */ _unregisterFromManager() { this.manager.unregisterRecordArray(this); }, willDestroy() { this._unregisterFromManager(); this._dissociateFromOwnRecords(); // TODO: we should not do work during destroy: // * when objects are destroyed, they should simply be left to do // * if logic errors do to this, that logic needs to be more careful during // teardown (ember provides isDestroying/isDestroyed) for this reason // * the exception being: if an dominator has a reference to this object, // and must be informed to release e.g. e.g. removing itself from th // recordArrayMananger set(this, 'content', null); set(this, 'length', 0); this._super(...arguments); }, /* @method _createSnapshot @private */ _createSnapshot(options) { // this is private for users, but public for ember-data internals return new SnapshotRecordArray(this, this.get('meta'), options); }, /* @method _takeSnapshot @private */ _takeSnapshot() { return get(this, 'content').map(internalModel => internalModel.createSnapshot()); } });