ember-m3
Version:
Alternative to @ember-data/model in which attributes and relationships are derived from API Payloads
229 lines (203 loc) • 7.12 kB
JavaScript
import { dasherize } from '@ember/string';
import M3ReferenceArray from './m3-reference-array';
import M3TrackedArray from './m3-tracked-array';
import { recordDataFor } from './-private';
import { EmbeddedMegamorphicModel, EmbeddedSnapshot } from './model';
import { A } from '@ember/array';
import {
computeAttributeReference,
computeNestedModel,
resolveReferencesWithRecords,
getOrCreateRecordFromRecordData,
resolveReferencesWithInternalModels,
} from './utils/resolve';
import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features';
let EmbeddedInternalModel;
if (!CUSTOM_MODEL_CLASS) {
// TODO: shouldn't need this anymore; this level of indirection for nested recordData isn't useful
EmbeddedInternalModel = class EmbeddedInternalModel {
constructor({ id, modelName, parentInternalModel, parentKey, parentIdx }) {
if (CUSTOM_MODEL_CLASS) {
//asert we dont need this class anymore
return;
}
this.id = id;
this.modelName = modelName;
let recordData = recordDataFor(parentInternalModel)._getChildRecordData(
parentKey,
parentIdx,
modelName,
id,
this
);
this._recordData = recordData;
this.parentInternalModel = parentInternalModel;
this.record = null;
}
createSnapshot() {
return new EmbeddedSnapshot(this.record);
}
changedAttributes() {
return this._recordData.changedAttributes();
}
};
}
function resolveReference(store, reference) {
let { id } = reference;
if (reference.type === null) {
// for schemas with a global id-space but multiple types, schemas may
// report a type of null
if (CUSTOM_MODEL_CLASS) {
let rd = store._globalM3RecordDataCache[reference.id];
return rd ? getOrCreateRecordFromRecordData(rd, store) : null;
} else {
let internalModel = store._globalM3Cache[id];
return internalModel ? internalModel.getRecord() : null;
}
} else {
// respect the user schema's type if provided
return id !== null && id !== undefined ? store.peekRecord(reference.type, reference.id) : null;
}
}
function resolveReferenceOrReferences(store, model, key, value, reference) {
if (Array.isArray(reference)) {
return resolveRecordArray(store, model, key, reference);
}
return resolveReference(store, reference);
}
export function resolveRecordArray(store, record, key, references) {
let recordArrayManager = store._recordArrayManager;
let array = M3ReferenceArray.create({
modelName: '-ember-m3',
content: A(),
store: store,
manager: recordArrayManager,
key,
record,
});
if (CUSTOM_MODEL_CLASS) {
let records = resolveReferencesWithRecords(store, references);
array._setObjects(records, false);
} else {
let internalModels = resolveReferencesWithInternalModels(store, references);
array._setInternalModels(internalModels, false);
}
return array;
}
/**
* There are two different type of values we have to worry about:
* 1. References
* 2. Nested Models
*
* Here is a mapping of input -> output:
* 1. Single reference -> resolved reference
* 2. Array of references -> RecordArray of resolved references
* 3. Single nested model -> EmbeddedMegaMorphicModel
* 4. Array of nested models -> array of EmbeddedMegaMorphicModel
*/
export function resolveValue(key, value, modelName, store, schema, record, parentIdx) {
const recordData = recordDataFor(record);
const schemaInterface = recordData.schemaInterface;
// First check to see if given value is either a reference or an array of references
let reference = computeAttributeReference(key, value, modelName, schemaInterface, schema);
if (reference !== undefined && reference !== null) {
return resolveReferenceOrReferences(store, record, key, value, reference);
}
let nested = computeNestedModel(key, value, modelName, schemaInterface, schema);
let content;
let isArray = false;
if (Array.isArray(nested)) {
isArray = true;
content = nested.map((v, i) => createNestedModel(store, record, recordData, key, v, i));
} else if (nested) {
content = createNestedModel(store, record, recordData, key, nested, parentIdx);
} else if (Array.isArray(value)) {
isArray = true;
content = value.map((v, i) =>
transferOrResolveValue(store, schema, record, recordData, modelName, key, v, i)
);
} else {
content = value;
}
if (isArray === true) {
return M3TrackedArray.create({
content: A(content),
key,
_value: value,
modelName,
store,
schema,
model: record,
});
}
return content;
}
function transferOrResolveValue(store, schema, record, recordData, modelName, key, value, index) {
if (value instanceof EmbeddedMegamorphicModel) {
// transfer ownership to the new RecordData
recordData._setChildRecordData(key, index, recordDataFor(value));
return value;
}
return resolveValue(key, value, modelName, store, schema, record, index);
}
function createNestedModel(store, record, recordData, key, nestedValue, parentIdx = null) {
if (parentIdx !== null && nestedValue instanceof EmbeddedMegamorphicModel) {
recordData._setChildRecordData(key, parentIdx, recordDataFor(nestedValue));
return nestedValue;
}
let modelName, nestedRecordData, internalModel;
if (CUSTOM_MODEL_CLASS) {
// TODO
// for backwards compat we will still need to dasherize,
// but it would be good to confirm that this is no longer a requirement
modelName = nestedValue.type ? dasherize(nestedValue.type) : null;
nestedRecordData = recordData._getChildRecordData(key, parentIdx, modelName, nestedValue.id);
} else {
internalModel = new EmbeddedInternalModel({
// nested models with ids is pretty misleading; all they really ought to need is type
id: nestedValue.id,
// maintain consistency with internalmodel.modelName, which is normalized
// internally within ember-data
modelName: nestedValue.type ? dasherize(nestedValue.type) : null,
parentInternalModel: record._internalModel,
parentKey: key,
parentIdx,
});
}
let nestedModel;
if (CUSTOM_MODEL_CLASS) {
nestedModel = EmbeddedMegamorphicModel.create({
store,
_parentModel: record,
_topModel: record._topModel,
_recordData: nestedRecordData,
});
} else {
nestedModel = EmbeddedMegamorphicModel.create({
store,
_parentModel: record,
_topModel: record._topModel,
_internalModel: internalModel,
});
internalModel.record = nestedModel;
nestedRecordData = recordDataFor(internalModel);
}
if (
!recordData.getServerAttr ||
(recordData.getServerAttr(key) !== null && recordData.getServerAttr(key) !== undefined)
) {
nestedRecordData.pushData(
{
attributes: nestedValue.attributes,
},
false,
false,
true
);
} else {
Object.keys(nestedValue.attributes).forEach(key => {
nestedRecordData.setAttr(key, nestedValue.attributes[key], true);
});
}
return nestedModel;
}