When using inheritance in model definitions, the behaviour is inconsistent.
As noted, the behaviour is inconsistent, and depends upon the order of occurrence of the parent/child models.
getModelSchemaRef) _are not_ used on the parent model, the schemas are generated correctly.My guess is there is some sort of caching mechanism (which makes a lot of sense) for the generation of OpenAPI schemas. If so, this mechanism is broken when dealing with inheritance.
Correct generation of both child and parent models' schemas, regardless of their occurrences' positions in controllers.
https://github.com/ricky92/loopback-next/tree/model-inheritance
Platform and node version: darwin x64 8.16.0
@loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
├── @loopback/[email protected]
└── [email protected]
Thank you @ricky92 for reporting the bug, I am able to reproduce it using your sandbox.
I did a quick check and I agree with your conclusion that our schema caching is not working correctly.
Here is the problematic code:
The caching problem seems to be fixed when I add {ownMetadataOnly: true} options as follows:
const cached = MetadataInspector.getClassMetadata(JSON_SCHEMA_KEY, ctor, {
ownMetadataOnly: true,
});
Observed schema:
{
"title": "TodoEx",
"properties": {
"id": { "type": "number" },
"title": { "type": "string" },
"desc": { "type": "string" },
"isComplete": { "type": "boolean" },
"remindAtAddress": { "type": "string" },
"remindAtGeo": { "type": "string" }
},
"required": [ "title" ]
}
I am not sure what is causing this second problem.
Anyhow, would you like to investigate this issue further and perhaps contribute a pull request to fix it? See Contributing code in LoopBack 4 and Submitting a pull request to LoopBack 4 to get started.
I ran into a similar symptom which, in my case at least (not sure if it's exactly the same as here), seems to be caused by the caching of MODEL_WITH_PROPERTIES_KEY in ModelMetadataHelper.getModelMetadata.
What's happening is that the call to MetadataInspector.getClassMetadata calls Reflector.getMetadata which calls Reflect.getMetadata which finally calls Reflect.OrdinaryGetMetadata which is this:
var hasOwn = OrdinaryHasOwnMetadata(MetadataKey, O, P);
if (hasOwn)
return OrdinaryGetOwnMetadata(MetadataKey, O, P);
var parent = OrdinaryGetPrototypeOf(O);
if (!IsNull(parent))
return OrdinaryGetMetadata(MetadataKey, parent, P);
return undefined;
And so what happens is that hasOwn comes out false (the MODEL_WITH_PROPERTIES_KEY cache is not built yet for this class), so it tries to fetch it from the parent, and the parent _is_ cached already, and so we return the cached value from the parent instead of building the cached value for the derived class.
I _think_ the fix may then be to have getModelMetadata _always_ pass {ownMetadataOnly: true} in the InspectionOptions argument to MetadataInspector.getClassMetadata for this specific case.
Workaround I used: Put all the properties in an abstract base class that does _not_ have a model decorator. Put the model decorator only on final leaf classes (one of which may have no properties of its own).
@mgabeler-lee-6rs that workaround solution worked for me. As a side-note for those of whom are going about this workaround for now. Make sure your relations are in your final leaf classes, not the base abstract class. Otherwise loopback will complain about the relation metadata not compiling correctly.
Something like...
Unhandled error in POST /users: 500 TypeError: Cannot read property 'idProperties' of undefined
at Function.getIdProperties (...\@loopback\repository\src\model.ts:271:28)
at Object.resolveHasOneMetadata (...\@loopback\repository\src\relations\has-one\has-one.helpers.ts:61:27)
at Object.createHasOneRepositoryFactory (...\@loopback\repository\src\relations\has-one\has-one-repository.factory.ts:52:16)
Possible causes
decorator function maybe introspecting the class too early
Possible Solution
Decorator should reference the raw metadata when the function is applied. We could have a late phase to do full introspection.
We need test cases to confirm this problem, and afterwards confirm that we've fixed the issue.
We need test cases to confirm this problem, and afterwards confirm that we've fixed the issue.
Based on https://github.com/strongloop/loopback-next/issues/3293#issuecomment-507643117, the problem can be reproduced using the sanbox provided in the bug report, see https://github.com/ricky92/loopback-next/tree/model-inheritance
Most helpful comment
I ran into a similar symptom which, in my case at least (not sure if it's exactly the same as here), seems to be caused by the caching of
MODEL_WITH_PROPERTIES_KEYinModelMetadataHelper.getModelMetadata.What's happening is that the call to
MetadataInspector.getClassMetadatacallsReflector.getMetadatawhich callsReflect.getMetadatawhich finally callsReflect.OrdinaryGetMetadatawhich is this:And so what happens is that
hasOwncomes out false (theMODEL_WITH_PROPERTIES_KEYcache is not built yet for this class), so it tries to fetch it from the parent, and the parent _is_ cached already, and so we return the cached value from the parent instead of building the cached value for the derived class.I _think_ the fix may then be to have
getModelMetadata_always_ pass{ownMetadataOnly: true}in theInspectionOptionsargument toMetadataInspector.getClassMetadatafor this specific case.Workaround I used: Put all the properties in an abstract base class that does _not_ have a
modeldecorator. Put themodeldecorator only on final leaf classes (one of which may have no properties of its own).