-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(cli): import LB3 models with a custom base class #4737
Conversation
@emonddr @deepakrkris @nabdelgadir @agnes512 please review |
8ff723b
to
abc4c07
Compare
@@ -33,7 +33,6 @@ describe('importLb3ModelDefinition', () => { | |||
expect(toJSON(modelData.settings)).to.deepEqual({ | |||
// By default, LB3 models are not strict | |||
strict: false, | |||
replaceOnPUT: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: replaceOnPUT
is a flag set by juggler, it's used for backwards compatibility with LoopBack 2.x.
Quoting from https://loopback.io/doc/en/lb4/Model.html:
replaceOnPUT
- This entry is no longer supported as LB4 controllers scaffolded by LB4 controller, PUT is always calling replaceById. Users are free to change the generated code to call patchById if needed.
'Adding %s (base of %s) to the list of imported models.', | ||
chalk.yellow(baseName), | ||
chalk.yellow(name), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is an example console output.
$ lb4 import-lb3-models ~/src/loopback/next/packages/cli/test/fixtures/import-lb3-models/app-using-model-inheritance.js
WARNING: This command is experimental and not feature-complete yet.
Learn more at https://loopback.io/doc/en/lb4/Importing-LB3-models.html
? Select models to import: Customer
Model Customer will be created in src/models/customer.model.ts
Adding UserBase (base of Customer) to the list of imported models.
Model UserBase will be created in src/models/user-base.model.ts
Adding User (base of UserBase) to the list of imported models.
Model User will be created in src/models/user.model.ts
Import of model relations is not supported yet. Skipping the following relations: accessTokens
Ignoring the following unsupported settings: acls
create src/models/customer.model.ts
create src/models/user-base.model.ts
create src/models/user.model.ts
update src/models/index.ts
update src/models/index.ts
update src/models/index.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just making sure I understand correctly, we're importing User
because UserBase
inherits from it? And do we always eventually want one model to extend from a built in model (e.g. in this case User
inheriting from Entity
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're importing
User
becauseUserBase
inherits from it?
Yes, exactly 👍
And do we always eventually want one model to extend from a built in model (e.g. in this case
User
inheriting fromEntity
)?
Yes. Every LoopBack 3 model has the LB3 built-in Model
at the top of the inheritance chain. Some models are inheriting from PersistedModel
(which inherits from Model
and is mapped to Entity
in LB4), some models are inheriting from KeyValueModel
(which inherits from Model
and is probably mapped to KeyValueModel
in LB4, I am not sure).
validateUpsert: true, | ||
idInjection: true | ||
} | ||
settings: {strict: false, forceId: false, validateUpsert: true, idInjection: true} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The following settings are no longer imported because they are inherited:
replaceOnPUT
Please note that forceId
, idInjection
and validateUpsert
are explicitly configured in coffee-shop's model JSON file, therefore they are preserved in the generated output. You can compare this line with User
settings below https://github.com/strongloop/loopback-next/pull/4737/files#diff-e9be610fe11bc2c954313d9283d0eda3R128-R137, where only strict
+ custom settings are generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM in general 👏 Just have a few questions.
if (!templateData.isModelBaseBuiltin) { | ||
const baseName = templateData.modelBaseClass; | ||
if ( | ||
!this.existingLb4ModelNames.includes(baseName) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to make sure that I understand it correctly, the reason we check the existence of the base model is because we need to import the custom base class and also it's parent class if that doesn't exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Let's say the application has:
AuditedPersistedModel
which is adding auditing functionality for all CRUD operations provided byPersistedModel
,- several models like
Product
,Category
andStore
, where all models are based onAuditedPersistedModel
.
When we run import for the first time and choose only Store
to import, we also need to import AuditedPersistedModel
to allow the new LB4 model to inherit from the correct base class (model).
When we run import for the second time and choose to import Product
and Category
, we should not import AuditedPersistedModel
again, because that could overwrite bits imported manually in the previous step. Instead, LB4 CLI will skip importing this base model.
// Core LB3 models like PersistedModel come with an id property that's | ||
// injected by juggler. We don't want to inherit that property, because | ||
// in LB4, we want models to define the id property explicitly. | ||
if (isCoreModel(modelCtor.base)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I am a bit confused. Does this mean if a custom model inherits a base model, it won't have the id property after importing to LB4 app?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When scaffolding code for an imported model, we want to skip all properties inherited from the base model. For example, if User
model has email
property and Customer
model inherits from User
, then the code for Customer
SHOULD NOT re-define email
property again. The property should be inherited instead, both at TypeScript and juggler-metadata level.
Now juggler's PersistedModel
always comes with id
property (in fact, the property is added to every model created by juggler, including the base Model
class), see ModelBuilder.prototype.define().
As a result, when deciding which properties belong to the model being imported (e.g. Product
) and which are inherited (e.g. from PersistedModel
), the primary key property id
was always skipped before I introduced this check, simply because it was considered as coming from the base model (PersistedModel
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
packages/cli/test/fixtures/import-lb3-models/app-using-model-inheritance.js
Outdated
Show resolved
Hide resolved
const def = migratePropertyDefinition(properties[prop]); | ||
const propDef = properties[prop]; | ||
|
||
// Skip the property if it was inherited from the base model (the parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reasonable to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just have a clarification question, but LGTM
'Adding %s (base of %s) to the list of imported models.', | ||
chalk.yellow(baseName), | ||
chalk.yellow(name), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So just making sure I understand correctly, we're importing User
because UserBase
inherits from it? And do we always eventually want one model to extend from a built in model (e.g. in this case User
inheriting from Entity
)?
We are using LB3 only for tests. CLI commands dealing with LB3 apps are using `loopback` module installed inside the LB3 app. Signed-off-by: Miroslav Bajtoš <[email protected]>
Signed-off-by: Miroslav Bajtoš <[email protected]>
Signed-off-by: Miroslav Bajtoš <[email protected]>
Signed-off-by: Miroslav Bajtoš <[email protected]>
abc4c07
to
8c5f3cf
Compare
The initial implementation of
lb4 import-lb3-models
was able to import only models inheriting from models that have a built-in counter-part in LB4:Model
,PersistedModel
,KeyValueModel
.In this pull request, I am implementing import of models inheriting from non-LB4-builtins. There are two major use cases:
User
.The changes are split into multiple smaller commits to make the patch easier to review.
Checklist
👉 Read and sign the CLA (Contributor License Agreement) 👈
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated👉 Check out how to submit a PR 👈