Skip to content

Conversation

@agnes512
Copy link
Contributor

@agnes512 agnes512 commented Jan 6, 2020

Fixes #3287 and #4209

1st commit: code changes
2nd commit: tests

Before

Example:

  • hasMany
    ? SourceModel: Customer
    ? DestModel: Order,
    ? Foreign key to define on the target model: customerId
    ? Source property name for the relation getter: `orders
    ? Allow Order queries to include data from related Customer instances? Yes
    ==> issues:

    • if generation gets aborted, part of code still gets generated in Model and Repository
    • the default value doesn't match with the code in /repository/relation
  • belongTo
    ? SourceModel: Order
    ? DestModel: Customer,
    ? Source property name for the relation getter: customerId
    ? Allow ..inclusion? Yes
    ==> issues:

    • if generation gets aborted, part of code still gets generated in Model and Repository
    • the default value doesn't match with the code in /repository/relation
    • generated code failed if the foreign key name is not the default value
    • generator doesn't respect custom relation name, the only relation it generates is targetModelName (e.g customer)

New changes

  • checks if corresponding repository exists
  • checks if pk exists and validates the name
  • checks if fk exists and validates the name
  • asks fk for both hasMany and belongsTo
  • asks relation name for both hasMany and belongsTo
  • checks if the relation name exists
  • allow custom relation name for belongsTo

Examples of new prompt:

  • hasMany
    ? Please select the relation type hasMany
    ? Please select source model Customer
    ? Please select target model Order
    ? Foreign key name to define on the target model c_id // not default value
    ? Source property name for the relation getter (will be the relation name) orders
    ? Allow Customer queries to include data from related Review instances? Yes

=>

// Customer property:
  @hasMany(() => Order, {keyTo: 'c_id'})  // custom fk
  orders: Order[];

// Order fk:
  @property({
    type: 'number',
  })
  c_id?: number;  // custom fk
  • belongsTo
    ? Please select the relation type belongsTo
    ? Please select source model Order
    ? Please select target model Customer
    ? Foreign key name to define on the source model c_id
    ? Relation name my_customer // not default name
    ? Allow Customer queries to include data from related Review instances? Yes

=>

//Order fk:
  @belongsTo(() => Customer, {name: 'my_customer'}) . // allow custom relation name
  c_id: number;    // allow custom fk name

For belongsTo, if the fk name is the same as the relation name, the prompt would ask the user to re-enter a new name to avoid a known issue: Relations with custom names not working

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@agnes512 agnes512 force-pushed the fix-lb4-relation branch 2 times, most recently from 479d9e0 to d492728 Compare January 8, 2020 20:38
@agnes512 agnes512 changed the title fix(cli): discard changes if lb4 relation fails fix(cli): discard changes if lb4 relation fails. Allow customized relation name for belongsTo Jan 8, 2020
Copy link
Contributor Author

@agnes512 agnes512 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a new prompt to ask the source key of belongsTo (e.g customerId). By comparing the source key name and the relation name, the generator would respect customized relation name, see issue #4209

},
];
// except the customized relation name, also need to check if the property name is the same as relation name
// or it will get 'navigational property' error when creating instances
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.artifactInfo.defaultRelationName = this._getDefaultRelationName();
// for hasMany && hasOne, the source key is the same as the relation name
const msg =
this.artifactInfo.relationType === 'belongsTo'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property name will be the source key and the relation name for hasMany and hasOne. So I separate it from belongsTo here.

@agnes512 agnes512 marked this pull request as ready for review January 8, 2020 20:51
@agnes512 agnes512 requested review from achrinza and derdeka January 8, 2020 20:53
@agnes512
Copy link
Contributor Author

agnes512 commented Jan 8, 2020

@derdeka @achrinza could you take a look? part of code fixes #4209. Thanks.

@bajtos bajtos requested review from elv1s and nabdelgadir January 9, 2020 07:59
Copy link
Member

@bajtos bajtos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am afraid I don't have bandwidth to review the changes in details, I'll leave the review to somebody else who is more familiar with this part of our code base.

> {
public readonly customerClass: BelongsToAccessor<CustomerClass, typeof OrderClass.prototype.orderNumber>;
public readonly customerClassCustNumber: BelongsToAccessor<CustomerClass, typeof OrderClass.prototype.orderNumber>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm what does Cust mean 🤔 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CustNumber is the name of the id property of customerClass model.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I change the generator to generate customerClassId instead to match with our code in /repository/relation

Copy link
Contributor

@jannyHou jannyHou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great effort on improving the complicated prompts in relation cli 👍 👍
I left some minor comments and questions.

);
// checks if the relation name already exists in repo
if (
relationUtils.doesPropertyExist(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious: I think the relation definition is in the model file, why do we have to use more steps to get the corresponding repository file and check the existence?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! From my understanding the model has the property (e.g orders or customerId). And the repository has the relation name (e.g orders or customer). They might have the same name but most of time they don't. For example, for belongsTo relation, the relation name is customer.

The generator throws errors if the repository already has the relation name defined. And the generator didn't have such checks before .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the repository has the relation name

Oh that's smart! 👍 makes perfect sense then

`this.${relationName} = ` +
`this.createBelongsToAccessorFor('` +
`${this.artifactInfo.relationName.replace(/Id$/, '')}',` +
`${relationName}',` +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here Id is no longer going to be replaced with empty string. Will this cause a regression?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have relationName defined above so we don't need it here anymore.


_getRepositoryRelationPropertyName() {
return utils.pluralize(utils.camelCase(this.artifactInfo.dstModelClass));
return this.artifactInfo.relationName;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're removing calls to the util.camelCase in several places. Will this cause a problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have relationName defined above so we can just use the value here.

@emonddr
Copy link
Contributor

emonddr commented Jan 9, 2020

The relation-based test cases pass (i ran them on my laptop), so that's great. Doesn't appear to be any regressions.
Some coverage fell though :S

@agnes512
Copy link
Contributor Author

agnes512 commented Jan 9, 2020

The relation-based test cases pass (i ran them on my laptop), so that's great. Doesn't appear to be any regressions.
Some coverage fell though :S

If you check the tests It is caused by the generator every time it does exit(err), not the actual code itself. I don't any way to skip it. If anyone knows please let me know 😄

Copy link
Member

@achrinza achrinza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious; do we also need to update the docs that reference the CLI prompt procedure?

See:

@agnes512
Copy link
Contributor Author

Just curious; do we also need to update the docs that reference the CLI prompt procedure?

See:

Yes, I will add them once the proposal looks good to most of you 😄

} else {
relationUtils.addProperty(classDeclaration, property);
}
// already chcked the existence of property before
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(sorry I am not very familiar with the relation cli which results in many questions ❔ ❔ ) Hmm, where is it checked? did a quick search of code before it but didn't find any clue 🙈

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here the property is the relation name, which has been checked in index file. Sorry the PR is big, I should've separated them.

throw new Error(
'Parameter ' + parameterName + ' already exist in the constructor.',
);
// no need to check if the getter already exists
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't parameter exist imply the relation already exists? And therefore an error should be thrown like the deleted code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The param is getter, such as @repository.getter('OrderRepository') protected orderRepositoryGetter: Getter<OrderRepository>. And I don't think this implies a certain relation name already exists. That's why I removed it.

And I was also thinking about some edge cases. For example, since we allow customized keyFrom, user could have two same relations with 2 models with two different names. In this case, they will share the same getter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we allow customized keyFrom, user could have two same relations with 2 models with two different names. In this case, they will share the same getter.

👍 Thank you I agree with you.
The property should be checked instead of parameter.

relationDecorator = [
{
name: 'belongsTo',
arguments: [`() => ${className}, {name: '${relationName}'}`],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused about defaultRelationName and sourceKeyName, wouldn't they be the same?
I find it hard to think of a user scenario that defaultRelationName, sourceKeyName and relationName are all different...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I will list out the differences:

Before, the generator asked foreign key, relationName for hasMany.
and it only asked relationName for belongTo if defaultRelationName doesn't exist. It used relationName as the foreign key name (e.g customerId). Then it use the target model as the real relation name (e.g `customer).

This is why how it dealt with belongsTo is problematic:

  1. if the default foreign key already exists, it didn't prompt
  2. if the default foreign key doesn't exists, it takes whatever it is, but the real relation name is still the target model, and this is different from how the belongsToAccessor generates the default name. For example: if you type in customer_id as your foreign key, the generator would have customer as the default relation name. But for the belongsToAccessor, the default relation name is customer_id instead of customer. That's why users are getting Cannot find type of undefined errors.

In order to allow custom fk and relation name for belongsTo, I added a new field sourceKeyName to represent the foreign key of belongsTo. And relationName to represent the real relation name.

The line defaultRelationName !== relationName here is to check if the relationName is customized. If so, it should be specified in the name field, just like how we documented in our docs Relation Metadata.

Therefore, it can have correct property:

  @belongsTo(() => Customer, {name: 'my_customer'}) // customized relation name
  customer_id: number; // customized fk

@agnes512 agnes512 force-pushed the fix-lb4-relation branch 3 times, most recently from 6d8e6a5 to 3a8932a Compare January 12, 2020 20:29
@agnes512 agnes512 force-pushed the fix-lb4-relation branch 4 times, most recently from 5d33f0d to 6c552fb Compare January 13, 2020 17:27
@agnes512 agnes512 force-pushed the fix-lb4-relation branch 2 times, most recently from 5fe3a6f to fb85d50 Compare January 14, 2020 13:42
Copy link
Contributor

@nabdelgadir nabdelgadir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(cli): cli does not respect custom relation getter name lb4 relation should discard changes when something failed

7 participants