Skip to content

Commit

Permalink
Merge 3475f5f into f3ccca2
Browse files Browse the repository at this point in the history
  • Loading branch information
Lokesh Mohanty committed Jan 28, 2020
2 parents f3ccca2 + 3475f5f commit 2c04d3e
Show file tree
Hide file tree
Showing 19 changed files with 2,121 additions and 116 deletions.
414 changes: 301 additions & 113 deletions docs/site/hasOne-relation.md → docs/site/HasOne-relation.md

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions docs/site/Relation-generator.md
Expand Up @@ -33,7 +33,7 @@ lb4 relation [options]
- `--sourceModel`: Source model.
- `--destinationModel`: Destination model.
- `--foreignKeyName`: Destination/Source model foreign key name for
HasMany/BelongsTo relation, respectively.
HasMany,HasOne/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 All @@ -46,7 +46,7 @@ Defining lb4 relation in one command line interface (cli):
```sh
lb4 relation --sourceModel=<sourceModel>
--destinationModel=<destinationModel> --foreignKeyName=<foreignKeyName>
--relationType=<hasMany|belongsTo> [--relationName=<relationName>] [--format]
--relationType=<hasMany|hasOne|belongsTo> [--relationName=<relationName>] [--format]
```

- `<relationType>` - Type of the relation that will be created between the
Expand Down Expand Up @@ -81,6 +81,7 @@ The tool will prompt you for:
source model and the target model. Supported relation types:
- [HasMany](HasMany-relation.md)
- [HasOne](HasOne-relation.md)
- [BelongsTo](BelongsTo-relation.md)
- **Name of the `source` model.** _(sourceModel)_ Prompts a list of available
Expand Down
2 changes: 1 addition & 1 deletion docs/site/Relations.md
Expand Up @@ -57,7 +57,7 @@ The articles on each type of relation above will show you how to leverage the
new relation engine to define and configure relations in your LoopBack
application.

To generate a `HasMany` or `BelongsTo` relation through the CLI, see
To generate a `HasMany`, `HasOne` or `BelongsTo` relation through the CLI, see
[Relation generator](Relation-generator.md).

## Limitations
Expand Down
197 changes: 197 additions & 0 deletions packages/cli/generators/relation/has-one-relation.generator.js
@@ -0,0 +1,197 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const path = require('path');
const BaseRelationGenerator = require('./base-relation.generator');
const relationUtils = require('./utils.generator');
const utils = require('../../lib/utils');

const CONTROLLER_TEMPLATE_PATH_HAS_ONE =
'controller-relation-template-has-one.ts.ejs';

module.exports = class HasOneRelationGenerator extends BaseRelationGenerator {
constructor(args, opts) {
super(args, opts);
}

async generateControllers(options) {
this.artifactInfo.sourceModelClassName = options.sourceModel;
this.artifactInfo.targetModelClassName = options.destinationModel;
this.artifactInfo.sourceRepositoryClassName =
this.artifactInfo.sourceModelClassName + 'Repository';
this.artifactInfo.controllerClassName =
this.artifactInfo.sourceModelClassName +
this.artifactInfo.targetModelClassName +
'Controller';
this.artifactInfo.paramSourceRepository = utils.camelCase(
this.artifactInfo.sourceModelClassName + 'Repository',
);

this.artifactInfo.sourceModelName = utils.toFileName(options.sourceModel);
this.artifactInfo.sourceModelPath = utils.pluralize(
this.artifactInfo.sourceModelName,
);
this.artifactInfo.targetModelName = utils.toFileName(
options.destinationModel,
);
this.artifactInfo.targetModelPath = utils.pluralize(
this.artifactInfo.targetModelName,
);
this.artifactInfo.targetModelRequestBody = utils.camelCase(
this.artifactInfo.targetModelName,
);
this.artifactInfo.relationPropertyName = options.relationName;
this.artifactInfo.sourceModelPrimaryKey = options.sourceModelPrimaryKey;
this.artifactInfo.sourceModelPrimaryKeyType =
options.sourceModelPrimaryKeyType;
this.artifactInfo.targetModelPrimaryKey = options.targetModelPrimaryKey;
this.artifactInfo.foreignKeyName = options.foreignKeyName;

const source = this.templatePath(CONTROLLER_TEMPLATE_PATH_HAS_ONE);

this.artifactInfo.name =
options.sourceModel + '-' + options.destinationModel;
this.artifactInfo.outFile =
utils.toFileName(this.artifactInfo.name) + '.controller.ts';

const dest = this.destinationPath(
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
);

this.copyTemplatedFiles(source, dest, this.artifactInfo);
await relationUtils.addExportController(
this,
path.resolve(this.artifactInfo.outDir, 'index.ts'),
this.artifactInfo.controllerClassName,
utils.toFileName(this.artifactInfo.name) + '.controller',
);
}

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 fktype = options.sourceModelPrimaryKeyType;
const isForeignKeyExist = options.doesForeignKeyExist;
const foreignKeyName = options.foreignKeyName;

const isDefaultForeignKey =
foreignKeyName === utils.camelCase(options.sourceModel) + 'Id';

let modelProperty;
const project = new relationUtils.AstLoopBackProject();

const sourceFile = relationUtils.addFileToProject(
project,
modelDir,
sourceModel,
);
const sourceClass = relationUtils.getClassObj(sourceFile, sourceModel);
relationUtils.doesRelationExist(sourceClass, relationName);

modelProperty = this.getHasOne(
targetModel,
relationName,
isDefaultForeignKey,
foreignKeyName,
);

relationUtils.addProperty(sourceClass, modelProperty);
const imports = relationUtils.getRequiredImports(targetModel, relationType);

relationUtils.addRequiredImports(sourceFile, imports);
await sourceFile.save();

const targetFile = relationUtils.addFileToProject(
project,
modelDir,
targetModel,
);
const targetClass = relationUtils.getClassObj(targetFile, targetModel);

if (isForeignKeyExist) {
if (
!relationUtils.isValidPropertyType(targetClass, foreignKeyName, fktype)
) {
throw new Error('foreignKey Type Error');
}
} else {
modelProperty = relationUtils.addForeignKey(foreignKeyName, fktype);
relationUtils.addProperty(targetClass, modelProperty);
targetClass.formatText();
await targetFile.save();
}
}

getHasOne(className, relationName, isDefaultForeignKey, foreignKeyName) {
let relationDecorator = [{
name: 'hasOne',
arguments: [`() => ${className}, {keyTo: '${foreignKeyName}'}`],
}, ];
if (isDefaultForeignKey) {
relationDecorator = [{
name: 'hasOne',
arguments: [`() => ${className}`]
}];
}

return {
decorators: relationDecorator,
name: relationName,
type: className,
};
}

_getRepositoryRequiredImports(dstModelClassName, dstRepositoryClassName) {
const importsArray = super._getRepositoryRequiredImports(
dstModelClassName,
dstRepositoryClassName,
);
importsArray.push({
name: 'HasOneRepositoryFactory',
module: '@loopback/repository',
});
return importsArray;
}

_getRepositoryRelationPropertyName() {
return this.artifactInfo.relationName;
}

_getRepositoryRelationPropertyType() {
return `HasOneRepositoryFactory<${utils.toClassName(
this.artifactInfo.dstModelClass,
)}, typeof ${utils.toClassName(
this.artifactInfo.srcModelClass,
)}.prototype.${this.artifactInfo.srcModelPrimaryKey}>`;
}

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

_registerInclusionResolverForRelation(classConstructor, options) {
const relationPropertyName = this._getRepositoryRelationPropertyName();
if (options.registerInclusionResolver) {
const statement =
`this.registerInclusionResolver(` +
`'${relationPropertyName}', this.${relationPropertyName}.inclusionResolver);`;
classConstructor.insertStatements(2, statement);
}
}
};
9 changes: 9 additions & 0 deletions packages/cli/generators/relation/index.js
Expand Up @@ -15,6 +15,7 @@ const relationUtils = require('./utils.generator');

const BelongsToRelationGenerator = require('./belongs-to-relation.generator');
const HasManyRelationGenerator = require('./has-many-relation.generator');
const HasOneRelationGenerator = require('./has-one-relation.generator');

const ERROR_INCORRECT_RELATION_TYPE = 'Incorrect relation type';
const ERROR_MODEL_DOES_NOT_EXIST = 'model does not exist.';
Expand Down Expand Up @@ -141,6 +142,11 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
utils.camelCase(this.artifactInfo.destinationModel),
);
break;
case relationUtils.relationType.hasOne:
defaultRelationName = utils.camelCase(
this.artifactInfo.destinationModel,
);
break;
}

return defaultRelationName;
Expand Down Expand Up @@ -515,6 +521,9 @@ module.exports = class RelationGenerator extends ArtifactGenerator {
case relationUtils.relationType.hasMany:
relationGenerator = new HasManyRelationGenerator(this.args, this.opts);
break;
case relationUtils.relationType.hasOne:
relationGenerator = new HasOneRelationGenerator(this.args, this.opts);
break;
}

try {
Expand Down
@@ -0,0 +1,110 @@
import {
Count,
CountSchema,
Filter,
repository,
Where,
} from '@loopback/repository';
import {
del,
get,
getModelSchemaRef,
getWhereSchemaFor,
param,
patch,
post,
requestBody,
} from '@loopback/rest';
import {
<%= sourceModelClassName %>,
<%= targetModelClassName %>,
} from '../models';
import {<%= sourceRepositoryClassName %>} from '../repositories';

export class <%= controllerClassName %> {
constructor(
@repository(<%= sourceRepositoryClassName %>) protected <%= paramSourceRepository %>: <%= sourceRepositoryClassName %>,
) { }

@get('/<%= sourceModelPath %>/{id}/<%= targetModelPath %>', {
responses: {
'200': {
description: '<%= sourceModelClassName %> has one <%= targetModelClassName %>',
content: {
'application/json': {
schema: getModelSchemaRef(<%= targetModelClassName %>),
},
},
},
},
})
async find(
@param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>,
@param.query.object('filter') filter?: Filter<<%= targetModelClassName %>>,
): Promise<<%= targetModelClassName %>[]> {
return this.<%= paramSourceRepository %>.<%= relationPropertyName %>(id).find(filter);
}

@post('/<%= sourceModelPath %>/{id}/<%= targetModelPath %>', {
responses: {
'200': {
description: '<%= sourceModelClassName %> model instance',
content: {'application/json': {schema: getModelSchemaRef(<%= targetModelClassName %>)}},
},
},
})
async create(
@param.path.<%= sourceModelPrimaryKeyType %>('id') id: typeof <%= sourceModelClassName %>.prototype.<%= sourceModelPrimaryKey %>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(<%= targetModelClassName %>, {
title: 'New<%= targetModelClassName %>In<%= sourceModelClassName %>',
exclude: ['<%= targetModelPrimaryKey %>'],
optional: ['<%= foreignKeyName %>']
}),
},
},
}) <%= targetModelRequestBody %>: Omit<<%= targetModelClassName %>, '<%= targetModelPrimaryKey %>'>,
): Promise<<%= targetModelClassName %>> {
return this.<%= paramSourceRepository %>.<%= relationPropertyName %>(id).create(<%= targetModelRequestBody %>);
}

@patch('/<%= sourceModelPath %>/{id}/<%= targetModelPath %>', {
responses: {
'200': {
description: '<%= sourceModelClassName %>.<%= targetModelClassName %> PATCH success count',
content: {'application/json': {schema: CountSchema}},
},
},
})
async patch(
@param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(<%= targetModelClassName %>, {partial: true}),
},
},
})
<%= targetModelRequestBody %>: Partial<<%= targetModelClassName %>>,
@param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where<<%= targetModelClassName %>>,
): Promise<Count> {
return this.<%= paramSourceRepository %>.<%= relationPropertyName %>(id).patch(<%= targetModelRequestBody %>, where);
}

@del('/<%= sourceModelPath %>/{id}/<%= targetModelPath %>', {
responses: {
'200': {
description: '<%= sourceModelClassName %>.<%= targetModelClassName %> DELETE success count',
content: {'application/json': {schema: CountSchema}},
},
},
})
async delete(
@param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>,
@param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where<<%= targetModelClassName %>>,
): Promise<Count> {
return this.<%= paramSourceRepository %>.<%= relationPropertyName %>(id).delete(where);
}
}
1 change: 1 addition & 0 deletions packages/cli/generators/relation/utils.generator.js
Expand Up @@ -13,6 +13,7 @@ const utils = require('../../lib/utils');
exports.relationType = {
belongsTo: 'belongsTo',
hasMany: 'hasMany',
hasOne: 'hasOne',
};

class AstLoopBackProject extends ast.Project {
Expand Down

0 comments on commit 2c04d3e

Please sign in to comment.