UNPKG

ember-m3

Version:

Alternative to @ember-data/model in which attributes and relationships are derived from API Payloads

343 lines (293 loc) 10.7 kB
import { get } from '@ember/object'; import { dasherize } from '@ember/string'; import EmberObject from '@ember/object'; import MutableArray from '@ember/array/mutable'; import { A } from '@ember/array'; import MegamorphicModel, { EmbeddedMegamorphicModel } from './model'; import { resolveReferencesWithInternalModels, resolveReferencesWithRecords } from './utils/resolve'; import { deferArrayPropertyChange, deferPropertyChange, flushChanges, } from './utils/notify-changes'; import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features'; import { recordDataToRecordMap, recordToRecordArrayMap } from './utils/caches'; import { recordIdentifierFor } from '@ember-data/store'; /** * M3RecordArray * * @class M3RecordArray */ let M3RecordArray; if (CUSTOM_MODEL_CLASS) { /** * M3RecordArray * * @class M3RecordArray */ M3RecordArray = class M3RecordArray extends EmberObject { // public RecordArray API init() { super.init(...arguments); this._references = []; this._objects = A(); this._resolved = false; this.store = this.store || null; } replace(idx, removeAmt, newRecords) { let addAmt = get(newRecords, 'length'); let newObjects = new Array(addAmt); if (addAmt > 0) { let _newRecords = A(newRecords); for (let i = 0; i < newObjects.length; ++i) { newObjects[i] = _newRecords.objectAt(i); } } this._objects.replace(idx, removeAmt, newObjects); this._registerWithObjects(newObjects); this._resolved = true; deferArrayPropertyChange(this.store, this, idx, removeAmt, addAmt); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); // eager change events on mutation as mutations are user entry points flushChanges(this.store); } objectAtContent(idx) { // TODO make this lazy again let record = this._objects[idx]; return record; } objectAt(idx) { this._resolve(); return this.objectAtContent(idx); } _removeObject(object) { if (this._resolved) { this._objects.removeObject(object); deferArrayPropertyChange(this.store, this, 0, 1, 0); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); // eager change events here; we're not processing payloads (that goes // through `_setInternalModels`); we're doing `unloadRecord` flushChanges(this.store); } else { for (let j = 0; j < this._references.length; ++j) { let { id, type } = this._references[j]; let dtype = type && dasherize(type); // TODO we might not need the second condition let identifier = recordIdentifierFor(object); if ((dtype === null || dtype === identifier.type) && id === identifier.id) { this._references.splice(j, 1); break; } } } } // Private API _setObjects(objects, triggerChange = true) { let originalLength = this._objects.length; if (triggerChange) { this._objects.replace(0, this._objects.length, objects); deferArrayPropertyChange(this.store, this, 0, originalLength, this._objects.length); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); } else { this._objects.splice(0, this._objects.length, ...objects); } this.setProperties({ isLoaded: true, isUpdating: false, }); this._registerWithObjects(objects); this._resolved = true; } _setReferences(references) { this._references = references; this._resolved = false; let originalLength = this._objects.length; this._objects = A(); deferArrayPropertyChange(this.store, this, 0, originalLength, this._objects.length); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); } _removeRecordData(recordData) { if (this._resolved) { let record = recordDataToRecordMap.get(recordData); if (!record) { return; } this._objects.removeObjects([record]); deferArrayPropertyChange(this.store, this, 0, 1, 0); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); // eager change events here; we're not processing payloads (that goes // we're doing `unloadRecord` flushChanges(this.store); } } _registerWithObjects(objects) { objects.forEach(object => { if (!object) { return; } associateRecordWithRecordArray(object, this); }); } _resolve() { if (this._resolved) { return; } if (this._references !== null) { let objects = resolveReferencesWithRecords(this.store, this._references); this._setObjects(objects, false); } this._resolved = true; } get length() { return this._resolved ? this._objects.length : this._references.length; } }; M3RecordArray.reopen(MutableArray); } else { M3RecordArray = class RecordArray extends EmberObject { // public RecordArray API init() { this._internalModels = A(); super.init(...arguments); this._references = []; this._resolved = false; this.store = this.store || null; } replace(idx, removeAmt, newRecords) { let addAmt = get(newRecords, 'length'); let newInternalModels = new Array(addAmt); if (addAmt > 0) { let _newRecords = A(newRecords); for (let i = 0; i < newInternalModels.length; ++i) { newInternalModels[i] = _newRecords.objectAt(i)._internalModel; } } this._internalModels.replace(idx, removeAmt, newInternalModels); this._registerWithInternalModels(newInternalModels); this._resolved = true; deferArrayPropertyChange(this.store, this, idx, removeAmt, addAmt); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); // eager change events on mutation as mutations are user entry points flushChanges(this.store); } objectAtContent(idx) { let internalModel = this._internalModels[idx]; return internalModel !== null && internalModel !== undefined ? internalModel.getRecord() : undefined; } objectAt(idx) { this._resolve(); return this.objectAtContent(idx); } // RecordArrayManager private api _pushInternalModels(internalModels) { this._resolve(); this._internalModels.pushObjects(internalModels); } _removeInternalModels(internalModels) { if (this._resolved) { this._internalModels.removeObjects(internalModels); deferArrayPropertyChange(this.store, this, 0, internalModels.length, 0); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); // eager change events here; we're not processing payloads (that goes // through `_setInternalModels`); we're doing `unloadRecord` flushChanges(this.store); } else { for (let i = 0; i < internalModels.length; ++i) { let internalModel = internalModels[i]; for (let j = 0; j < this._references.length; ++j) { let { id, type } = this._references[j]; let dtype = type && dasherize(type); if ((dtype === null || dtype === internalModel.modelName) && id === internalModel.id) { this._references.splice(j, 1); break; } } } } } // Private API _setInternalModels(internalModels, triggerChange = true) { let originalLength = this._internalModels.length; this._internalModels.replace(0, this._internalModels.length, internalModels); if (triggerChange) { deferArrayPropertyChange(this.store, this, 0, originalLength, this._internalModels.length); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); } this.setProperties({ isLoaded: true, isUpdating: false, }); this._registerWithInternalModels(internalModels); this._resolved = true; } _setReferences(references) { this._references = references; this._resolved = false; let originalLength = this._internalModels.length; this._internalModels = A(); deferArrayPropertyChange(this.store, this, 0, originalLength, this._internalModels.length); deferPropertyChange(this.store, this, '[]'); deferPropertyChange(this.store, this, 'length'); } _registerWithInternalModels(internalModels) { for (let i = 0, l = internalModels.length; i < l; i++) { let internalModel = internalModels[i]; // allow refs to point to resources not in the store // TODO: instead add a schema missing ref hook; #254 if (internalModel !== null && internalModel !== undefined) { internalModel._recordArrays.add(this); } } } _resolve() { if (this._resolved) { return; } if (this._references !== null) { let internalModels = resolveReferencesWithInternalModels(this.store, this._references); this._setInternalModels(internalModels, false); } this._resolved = true; } get length() { return this._resolved ? this._internalModels.length : this._references.length; } }; M3RecordArray.reopen(MutableArray); } export function associateRecordWithRecordArray(record, recordArray) { if (record instanceof EmbeddedMegamorphicModel) { // embedded models can be added across tracked arrays (although this is // weird) but since they can't be unloaded there's no need to associate the // array with the model // // unloading the top model after adding one of its embedded models to some // other tracked array is undefined behaviour return; } if (CUSTOM_MODEL_CLASS) { if (record instanceof MegamorphicModel) { record._recordData._recordArrays.add(recordArray); } else { let recordArrays = recordToRecordArrayMap.get(record); if (!recordArrays) { recordToRecordArrayMap.set(record, [recordArray]); } else { recordArrays.push(recordArray); } } } else { record._internalModel._recordArrays.add(recordArray); } } export default M3RecordArray;