From 4a293d3160f75b14c70b4d170a166d866be07a11 Mon Sep 17 00:00:00 2001 From: Kevin Delisle Date: Wed, 20 Dec 2017 10:44:36 -0500 Subject: [PATCH] feat(repository): helper function for getting Model metadata --- .../repository/src/decorators/metadata.ts | 39 ++++++++ packages/repository/src/decorators/model.ts | 1 + packages/repository/src/index.ts | 1 + .../test/unit/decorator/metadata.ts | 93 +++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 packages/repository/src/decorators/metadata.ts create mode 100644 packages/repository/test/unit/decorator/metadata.ts diff --git a/packages/repository/src/decorators/metadata.ts b/packages/repository/src/decorators/metadata.ts new file mode 100644 index 000000000000..de25b268dd8f --- /dev/null +++ b/packages/repository/src/decorators/metadata.ts @@ -0,0 +1,39 @@ +import {InspectionOptions, MetadataInspector} from '@loopback/context'; +import {MODEL_PROPERTIES_KEY, MODEL_WITH_PROPERTIES_KEY} from '../'; +import {ModelDefinition, PropertyDefinition} from '../../index'; +export class ModelMetadataHelper { + /** + * A utility function to simplify retrieving metadata from a target model and + * its properties. + * @param target The class from which to retrieve metadata. + * @param options An options object for the MetadataInspector to customize + * the output of the metadata retrieval functions. + */ + static getModelMetadata(target: Function, options?: InspectionOptions) { + let classDef = MetadataInspector.getClassMetadata( + MODEL_WITH_PROPERTIES_KEY, + target, + options, + ); + // Return the cached value, if it exists. + // XXX(kjdelisle): If we're going to support dynamic updates, then this + // will be problematic in the future, since it will never update. + if (classDef) { + return classDef; + } else { + const meta = new ModelDefinition( + Object.assign({name: target.name}, classDef), + ); + meta.properties = Object.assign( + {}, + MetadataInspector.getAllPropertyMetadata( + MODEL_PROPERTIES_KEY, + target.prototype, + options, + ), + ); + MetadataInspector.defineMetadata(MODEL_WITH_PROPERTIES_KEY, meta, target); + return meta; + } + } +} diff --git a/packages/repository/src/decorators/model.ts b/packages/repository/src/decorators/model.ts index 4448f428cac3..3a9e67cf428b 100644 --- a/packages/repository/src/decorators/model.ts +++ b/packages/repository/src/decorators/model.ts @@ -17,6 +17,7 @@ import { export const MODEL_KEY = 'loopback:model'; export const MODEL_PROPERTIES_KEY = 'loopback:model-properties'; +export const MODEL_WITH_PROPERTIES_KEY = 'loopback:model-and-properties'; type PropertyMap = MetadataMap; diff --git a/packages/repository/src/index.ts b/packages/repository/src/index.ts index 3207690eac54..b1069dca6a7f 100644 --- a/packages/repository/src/index.ts +++ b/packages/repository/src/index.ts @@ -7,6 +7,7 @@ export * from './common-types'; export * from './decorators/model'; export * from './decorators/repository'; export * from './decorators/relation'; +export * from './decorators/metadata'; export * from './types'; export * from './model'; export * from './query'; diff --git a/packages/repository/test/unit/decorator/metadata.ts b/packages/repository/test/unit/decorator/metadata.ts new file mode 100644 index 000000000000..0d8abd4f694c --- /dev/null +++ b/packages/repository/test/unit/decorator/metadata.ts @@ -0,0 +1,93 @@ +import {ModelMetadataHelper} from '../../../src'; +import { + property, + model, + ModelDefinition, + MODEL_KEY, + MODEL_WITH_PROPERTIES_KEY, +} from '../../..'; +import {expect} from '@loopback/testlab'; +import {MetadataInspector} from '@loopback/context'; + +describe('Repository', () => { + describe('getAllClassMetadata', () => { + @model() + class Colour { + @property({}) + rgb: string; + } + @model() + class Widget { + @property() id: number; + @property.array(Colour) colours: Colour[]; + } + + @model() + class Samoflange { + id: number; + name: string; + canRotate: boolean; + } + + @model() + class Phlange { + @property() id: number; + @property() canFlap: boolean; + @property.array(Colour) colours: Colour[]; + } + + it('retrieves metadata for classes with @model', () => { + const meta = ModelMetadataHelper.getModelMetadata(Samoflange); + expect(meta).to.deepEqual( + new ModelDefinition({ + name: 'Samoflange', + properties: {}, + settings: new Map(), + }), + ); + }); + + it('retrieves metadata for classes with @model and @property', () => { + const meta = ModelMetadataHelper.getModelMetadata(Widget); + expect(meta).to.deepEqual( + new ModelDefinition({ + properties: { + id: { + type: Number, + }, + colours: { + array: true, + type: Colour, + }, + }, + settings: new Map(), + name: 'Widget', + }), + ); + }); + + it('returns cached metadata instead of recreating it', () => { + const classMeta = MetadataInspector.getClassMetadata( + MODEL_KEY, + Phlange, + ) as ModelDefinition; + classMeta.properties = { + foo: { + type: String, + }, + }; + // Intentionally change the metadata to be different from the Phlange + // class metadata + MetadataInspector.defineMetadata( + MODEL_WITH_PROPERTIES_KEY, + classMeta, + Phlange, + ); + + const meta = ModelMetadataHelper.getModelMetadata( + Phlange, + ) as ModelDefinition; + expect(meta.properties).to.eql(classMeta.properties); + }); + }); +});