UNPKG

ember-m3

Version:

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

213 lines (190 loc) 7.47 kB
import Service, { inject } from '@ember/service'; import { defineProperty } from '@ember/object'; import { assert } from '@ember/debug'; import DefaultSchema from './m3-schema'; import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features'; let useComputeAttributeCache = new WeakMap(); export default class SchemaManager extends Service { /** * Determines whether an attribute is a reference. * If it is not, return `null` or `undefined`. * Otherwise return an object with properties: * - `id` The id of the referenced model (either m3 or `@ember-data/model`) * - `type` The type of the referenced model (either m3 or `@ember-data/model`) * `null` is also a valid type in which case `id` will be looked up in a global cache. * * Note that attribute references are all treated as synchronous. * There is no ember-m3 analogue to `@ember-data/model` async relationships. * * @param {string} key * @param {Object} value * @param {string} modelName * @param {M3SchemaInterface} schemaInterface * @returns {Object} */ computeAttributeReference(key, value, modelName, schemaInterface) { return this.get('schema').computeAttributeReference(key, value, modelName, schemaInterface); } /** * Determines whether `value` is a nested model and compute * a new m3 model rather than returning a simple object if so. * * @param {string} key * @param {Object} value * @param {string} modelName * @param {schemaInterface} schemaInterface * @returns {Object} */ computeNestedModel(key, value, modelName, schemaInterface) { return this.get('schema').computeNestedModel(key, value, modelName, schemaInterface); } computeAttribute(key, value, modelName, schemaInterface) { return this.get('schema').computeAttribute(key, value, modelName, schemaInterface); } useComputeAttribute() { let schema = this.get('schema'); let useComputeAttribute = useComputeAttributeCache.get(schema); if (useComputeAttribute === undefined) { let defaultPrototype = DefaultSchema.prototype; let isComputeAttributeDefault = defaultPrototype.computeAttribute === schema.computeAttribute; useComputeAttribute = schema.computeAttribute && !isComputeAttributeDefault; useComputeAttributeCache.set(schema, useComputeAttribute); } return useComputeAttribute; } /** * Calls the schema's useUnderlyingErrorsValue passing in modelName * * @param {string} modelName - Name of model to determine if `errors` property in the payload should be used * @returns {boolean} */ useUnderlyingErrorsValue(modelName) { let schema = this.get('schema'); return schema.useUnderlyingErrorsValue(modelName); } /** * Whether or not ember-m3 should handle this `modelName`. * * @param {string} modelName * @returns {boolean} */ includesModel(modelName) { return this.get('schema').includesModel(modelName); } /* * Whether we should instantiate a record that has native proxy access */ useNativeProperties(modelName) { if (this.get('schema').useNativeProperties) { if (!CUSTOM_MODEL_CLASS) { throw new Error( 'In order to use nativeProperties you need to be on a version of Ember Data 3.28 or higher' ); } return this.get('schema').useNativeProperties(modelName); } else { return undefined; } } /** * If the model name is a projection over some base type, return that base * type. If the model name is not a projection, return null. * * Indicating that a model type is a projection over another means that any * attributes they have in common will be kept cache-consistent. * * @param {string} projectionModelName * @returns {string} */ computeBaseModelName(projectionModelName) { let result = this.get('schema').computeBaseModelName(projectionModelName); assert( `computeBaseModelName('${projectionModelName}') === '${result}'. This creates a projection cycle. If ${projectionModelName} is not a projection, return null from computeBaseModelName.`, typeof result !== 'string' || result !== projectionModelName ); return result; } isAttributeIncluded(modelName, attrName) { let whitelist = this._modelSchemaProperty(modelName, 'attributes'); return !whitelist || whitelist.includes(attrName); } getDefaultValue(modelName, keyName) { let defaults = this._modelSchemaProperty(modelName, 'defaults'); if (!defaults) { return; } return defaults[keyName]; } getAttributeAlias(modelName, attrName) { let aliases = this._modelSchemaProperty(modelName, 'aliases'); if (!aliases) { return; } return aliases[attrName]; } // TODO: probably need a better function name, e.g. computeAttributeNames /** * Compute the actual attribute names, default just return the array passed in. * * @param {Array<string>} keys * @param {string} modelName * @returns {Array<string>} */ computeAttributes(keys, modelName) { let schema = this.get('schema'); if (schema.computeAttributes && typeof schema.computeAttributes === 'function') { return schema.computeAttributes(keys, modelName); } return keys; } /** * Update the RecordData with raw value instead of resolved value * * @param {string} modelName * @param {string} attrName * @param {Object} value * @param {M3SchemaInterface} schemaInterface */ setAttribute(modelName, attrName, value, schemaInterface) { this.get('schema').setAttribute(modelName, attrName, value, schemaInterface); } isAttributeResolved(modelName, attrName, value, schemaInterface) { return this.get('schema').isAttributeResolved(modelName, attrName, value, schemaInterface); } transformValue(modelName, attrName, value) { let transforms = this._modelSchemaProperty(modelName, 'transforms'); let transform = transforms && transforms[attrName]; return transform ? transform(value) : value; } _modelSchema(modelName) { let models = this.get('schema').models; return models && models[modelName]; } /** * Access property of the type-specific information, including: * - `attributes` A list of whitelisted attributes. It is recommended to omit * this unless you explicitly want to prevent unknown properties returned in * the API payload from being read. If present, it is an array of strings that * list whitelisted attributes. Reads of non-whitelisted properties will * return `undefined`. * * - `defaults` An object whose key-value pairs map attribute names to default * values. Reads of properties not included in the API will return the default * value instead, if it is specified in the schema. * * - `aliases` Alternate names for payload attributes. Aliases are read-only, ie * equivalent to `Ember.computed.reads` and not `Ember.computed.alias` * * - `transforms` An object whose key-value pairs map attribute names to * functions that transform their values. * * @param {string} modelName * @param {string} property 'attributes'|'defaults'|'aliases'|'transforms' * @returns {Object} */ _modelSchemaProperty(modelName, property) { let modelSchema = this._modelSchema(modelName); return modelSchema && modelSchema[property]; } } defineProperty(SchemaManager.prototype, 'schema', inject('m3-schema'));