Skip to content

Commit

Permalink
Merge ad21b69 into c1cff12
Browse files Browse the repository at this point in the history
  • Loading branch information
agnes512 committed Jan 14, 2020
2 parents c1cff12 + ad21b69 commit af19515
Show file tree
Hide file tree
Showing 17 changed files with 477 additions and 323 deletions.
5 changes: 5 additions & 0 deletions docs/site/BelongsTo-relation.md
Expand Up @@ -43,6 +43,11 @@ related routes, you need to perform the following steps:
This section describes how to define a `belongsTo` relation at the model level
using the `@belongsTo` decorator to define the constraining foreign key.

LB4 also provides an CLI tool `lb4 relation` to generate `belongsTo` relation
for you. Before you check out the
[`Relation Generator`](https://loopback.io/doc/en/lb4/Relation-generator.html)
page, read on to learn how you can define relations to meet your requirements.

### Relation Metadata

LB4 uses three `keyFrom`, `keyTo` and `name` fields in the `belongsTo` relation
Expand Down
5 changes: 5 additions & 0 deletions docs/site/HasMany-relation.md
Expand Up @@ -181,6 +181,11 @@ export interface OrderRelations {
export type OrderWithRelations = Order & OrderRelations;
```

LB4 also provides an CLI tool `lb4 relation` to generate `hasMany` relation for
you. Before you check out the
[`Relation Generator`](https://loopback.io/doc/en/lb4/Relation-generator.html)
page, read on to learn how you can define relations to meet your requirements.

### Relation Metadata

LB4 uses three `keyFrom`, `keyTo` and `name` fields in the `hasMany` relation
Expand Down
32 changes: 23 additions & 9 deletions docs/site/Relation-generator.md
Expand Up @@ -32,7 +32,8 @@ lb4 relation [options]
- `--relationType`: Relation type.
- `--sourceModel`: Source model.
- `--destinationModel`: Destination model.
- `--foreignKeyName`: Destination model foreign key name.
- `--foreignKeyName`: Destination/Source model foreign key name for
HasMany/BelongsTo relation, respectively.
- `--relationName`: Relation name.
- `-c`, `--config`: JSON file name or value to configure options.
- `-y`, `--yes`: Skip all confirmation prompts with default or provided value.
Expand Down Expand Up @@ -88,18 +89,31 @@ The tool will prompt you for:
- **Name of the `target` model.** _(targetModel)_ Prompts a list of available
models to choose from as the target model of the relation.
- **Name of the `Source property`.** _(relationName)_ Prompts for the Source
property name. Note: Leave blank to use the default.
- **Name of the `foreign key`.** _(relationName)_ Prompts a property name that
references the primary key property of the another model. Note: Leave blank to
use the default.
Default values: `<foreignKeyName>` + `Id` in camelCase, e.g `categoryId`
- **Name of the `relation`.** _(relationName)_ Prompts for the Source property
name. Note: Leave blank to use the default.
Default values:
- `<targetModel><targetModelPrimaryKey>` for `belongsTo` relations, e.g.
`categoryId`
- plural form of `<targetModel>` for `hasMany` relations, e.g. `products`
- **Name of Foreign key** _(foreignKeyName)_ to be created in target model. For
hasMany relation type only, default: `<sourceModel><sourceModelPrimaryKey>`.
Note: Leave blank to use the default.
- Based on the foreign key for `belongsTo` relations, e.g. when the foreign
key is the default name, e.g `categoryId`, the default relation name is
`category`.
{% include warning.html content="Based on your input, the default foreign key name might be the same as the default relation name, especially for belongsTo relation. Please name them differently to avoid a known issue [Navigational Property Error](https://github.com/strongloop/loopback-next/issues/4392)
" lang=page.lang %}
The generator has some limitations. It only asks the most basic factors for
generating relations. For example, it uses the id property as the source key.
LB4 allows you customize the source key name, the foreign key name, the relation
name, and even the DB column name. Check the
[Relation Metadata](HasMany-relation.md#relation-metadata) section in each
relation to learn how you can define relations.
### Output
Expand Down
18 changes: 6 additions & 12 deletions packages/cli/generators/relation/base-relation.generator.js
Expand Up @@ -39,12 +39,12 @@ module.exports = class BaseRelationGenerator extends ArtifactGenerator {
}

async generateAll(options) {
this._setupGenerator();
await this.generateControllers(options);
this._setupGenerator();
await this.generateModels(options);
this._setupGenerator();
await this.generateRepositories(options);
this._setupGenerator();
await this.generateControllers(options);
}

async generateControllers(options) {
Expand Down Expand Up @@ -98,23 +98,17 @@ module.exports = class BaseRelationGenerator extends ArtifactGenerator {
type: this._getRepositoryRelationPropertyType(),
};

if (relationUtils.doesPropertyExist(classDeclaration, property.name)) {
throw new Error(
'property ' + property.name + ' already exist in the repository.',
);
} else {
relationUtils.addProperty(classDeclaration, property);
}
// already chcked the existence of property before
relationUtils.addProperty(classDeclaration, property);
}

_addParametersToRepositoryConstructor(classConstructor) {
const parameterName =
utils.camelCase(this.artifactInfo.dstRepositoryClassName) + 'Getter';

if (relationUtils.doesParameterExist(classConstructor, parameterName)) {
throw new Error(
'Parameter ' + parameterName + ' already exist in the constructor.',
);
// no need to check if the getter already exists
return;
}

classConstructor.addParameter({
Expand Down
58 changes: 45 additions & 13 deletions packages/cli/generators/relation/belongs-to-relation.generator.js
Expand Up @@ -73,12 +73,16 @@ module.exports = class BelongsToRelationGenerator extends BaseRelationGenerator
}

async generateModels(options) {
// for repo to generate relation name
this.artifactInfo.relationName = options.relationName;
const modelDir = this.artifactInfo.modelDir;
const sourceModel = options.sourceModel;

const targetModel = options.destinationModel;
const relationType = options.relationType;
const relationName = options.relationName;
const defaultRelationName = options.defaultRelationName;
const foreignKeyName = options.foreignKeyName;
const fktype = options.destinationModelPrimaryKeyType;

const project = new relationUtils.AstLoopBackProject();
Expand All @@ -88,10 +92,16 @@ module.exports = class BelongsToRelationGenerator extends BaseRelationGenerator
sourceModel,
);
const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel);

relationUtils.doesRelationExist(sourceClass, relationName);

const modelProperty = this.getBelongsTo(targetModel, relationName, fktype);
// this checks if the foreign key already exists, so the 2nd param should be foreignKeyName
relationUtils.doesRelationExist(sourceClass, foreignKeyName);

const modelProperty = this.getBelongsTo(
targetModel,
relationName,
defaultRelationName,
foreignKeyName,
fktype,
);

relationUtils.addProperty(sourceClass, modelProperty);
const imports = relationUtils.getRequiredImports(targetModel, relationType);
Expand All @@ -101,10 +111,32 @@ module.exports = class BelongsToRelationGenerator extends BaseRelationGenerator
await sourceFile.save();
}

getBelongsTo(className, relationName, fktype) {
getBelongsTo(
className,
relationName,
defaultRelationName,
foreignKeyName,
fktype,
) {
// checks if relation name is customized
let relationDecorator = [
{
name: 'belongsTo',
arguments: [`() => ${className}`],
},
];
// already checked if the relation name is the same as the source key before
if (defaultRelationName !== relationName) {
relationDecorator = [
{
name: 'belongsTo',
arguments: [`() => ${className}, {name: '${relationName}'}`],
},
];
}
return {
decorators: [{name: 'belongsTo', arguments: [`() => ${className}`]}],
name: relationName,
decorators: relationDecorator,
name: foreignKeyName,
type: fktype,
};
}
Expand All @@ -122,7 +154,7 @@ module.exports = class BelongsToRelationGenerator extends BaseRelationGenerator
}

_getRepositoryRelationPropertyName() {
return utils.camelCase(this.artifactInfo.dstModelClass);
return this.artifactInfo.relationName;
}

_initializeProperties(options) {
Expand All @@ -140,22 +172,22 @@ module.exports = class BelongsToRelationGenerator extends BaseRelationGenerator
}

_addCreatorToRepositoryConstructor(classConstructor) {
const relationPropertyName = this._getRepositoryRelationPropertyName();
const relationName = this.artifactInfo.relationName;
const statement =
`this.${relationPropertyName} = ` +
`this.${relationName} = ` +
`this.createBelongsToAccessorFor('` +
`${this.artifactInfo.relationName.replace(/Id$/, '')}',` +
`${relationName}',` +
` ${utils.camelCase(this.artifactInfo.dstRepositoryClassName)}` +
`Getter,);`;
classConstructor.insertStatements(1, statement);
}

_registerInclusionResolverForRelation(classConstructor, options) {
const relationPropertyName = this._getRepositoryRelationPropertyName();
const relationName = this.artifactInfo.relationName;
if (options.registerInclusionResolver) {
const statement =
`this.registerInclusionResolver(` +
`'${relationPropertyName}', this.${relationPropertyName}.inclusionResolver);`;
`'${relationName}', this.${relationName}.inclusionResolver);`;
classConstructor.insertStatements(2, statement);
}
}
Expand Down
Expand Up @@ -74,6 +74,8 @@ module.exports = class HasManyRelationGenerator extends BaseRelationGenerator {
}

async generateModels(options) {
// for repo to generate relation name
this.artifactInfo.relationName = options.relationName;
const modelDir = this.artifactInfo.modelDir;
const sourceModel = options.sourceModel;

Expand Down Expand Up @@ -166,7 +168,7 @@ module.exports = class HasManyRelationGenerator extends BaseRelationGenerator {
}

_getRepositoryRelationPropertyName() {
return utils.pluralize(utils.camelCase(this.artifactInfo.dstModelClass));
return this.artifactInfo.relationName;
}

_getRepositoryRelationPropertyType() {
Expand Down

0 comments on commit af19515

Please sign in to comment.