diff --git a/packages/cli/generators/relation/belongs-to-relation.generator.js b/packages/cli/generators/relation/belongs-to-relation.generator.js index f5a1d46e180d..f26c7c3e8232 100644 --- a/packages/cli/generators/relation/belongs-to-relation.generator.js +++ b/packages/cli/generators/relation/belongs-to-relation.generator.js @@ -49,6 +49,7 @@ module.exports = class BelongsToRelationGenerator extends ( ); this.artifactInfo.relationPropertyName = options.relationName; + this.artifactInfo.keyTo = options.keyTo; this.artifactInfo.targetModelPrimaryKey = options.destinationModelPrimaryKey; this.artifactInfo.targetModelPrimaryKeyType = @@ -85,7 +86,11 @@ module.exports = class BelongsToRelationGenerator extends ( const relationName = options.relationName; const defaultRelationName = options.defaultRelationName; const foreignKeyName = options.foreignKeyName; - const fktype = options.destinationModelPrimaryKeyType; + const keyTo = options.keyTo; + const fktype = keyTo + ? relationUtils.getModelPropertyType(modelDir, targetModel, keyTo) || + options.destinationModelPrimaryKeyType + : options.destinationModelPrimaryKeyType; const project = new relationUtils.AstLoopBackProject(); const sourceFile = relationUtils.addFileToProject( @@ -103,6 +108,7 @@ module.exports = class BelongsToRelationGenerator extends ( defaultRelationName, foreignKeyName, fktype, + keyTo, ); relationUtils.addProperty(sourceClass, modelProperty); @@ -123,12 +129,19 @@ module.exports = class BelongsToRelationGenerator extends ( defaultRelationName, foreignKeyName, fktype, + keyTo, ) { + const keyToOption = keyTo ? `, keyTo: '${keyTo}'` : ''; + // checks if relation name is customized let relationDecorator = [ { name: 'belongsTo', - arguments: [`() => ${className}`], + arguments: [ + keyTo + ? `() => ${className}, {keyTo: '${keyTo}'}` + : `() => ${className}`, + ], }, ]; // already checked if the relation name is the same as the source key before @@ -136,7 +149,9 @@ module.exports = class BelongsToRelationGenerator extends ( relationDecorator = [ { name: 'belongsTo', - arguments: [`() => ${className}, {name: '${relationName}'}`], + arguments: [ + `() => ${className}, {name: '${relationName}'${keyToOption}}`, + ], }, ]; } diff --git a/packages/cli/generators/relation/index.js b/packages/cli/generators/relation/index.js index 0c7cb5703e50..163c49ecb422 100644 --- a/packages/cli/generators/relation/index.js +++ b/packages/cli/generators/relation/index.js @@ -761,6 +761,10 @@ module.exports = class RelationGenerator extends ArtifactGenerator { debug('Invoke generator...'); + if (this.options.keyTo) { + this.artifactInfo.keyTo = this.options.keyTo; + } + let relationGenerator; this.artifactInfo.name = this.artifactInfo.relationType; diff --git a/packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs b/packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs index 1451cb6cfb7b..e70f3ec0cb78 100644 --- a/packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs +++ b/packages/cli/generators/relation/templates/controller-relation-template-belongs-to.ts.ejs @@ -21,7 +21,7 @@ export class <%= controllerClassName %> { @get('/<%= sourceModelPath %>/{id}/<%= targetModelName %>', { responses: { '200': { - description: '<%= targetModelClassName %> belonging to <%= sourceModelClassName %>', + description: '<%= targetModelClassName %> belonging to <%= sourceModelClassName %><%= keyTo ? " (resolved via " + targetModelClassName + "." + keyTo + ")" : "" %>', content: { 'application/json': { schema: getModelSchemaRef(<%= targetModelClassName %>), diff --git a/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js b/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js index 3c7de80928d5..9f2bbb360d26 100644 --- a/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js +++ b/packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js @@ -373,3 +373,147 @@ export class Employee extends Entity { } `; + + +exports[`lb4 relation generates belongsTo relation with keyTo option generated model includes keyTo in belongsTo decorator 1`] = ` +import {Entity, model, property, belongsTo} from '@loopback/repository'; +import {Customer} from './customer.model'; + +@model() +export class Order extends Entity { + @property({ + type: 'number', + id: true, + default: 0, + }) + id?: number; + + @property({ + type: 'string', + }) + name?: string; + + @belongsTo(() => Customer, {keyTo: 'name'}) + customerId: string; + + constructor(data?: Partial) { + super(data); + } +} + +`; + + +exports[`lb4 relation generates belongsTo relation with keyTo option generated controller description includes resolved via reference 1`] = ` +import { + repository, +} from '@loopback/repository'; +import { + param, + get, + getModelSchemaRef, +} from '@loopback/rest'; +import { + Order, + Customer, +} from '../models'; +import {OrderRepository} from '../repositories'; + +export class OrderCustomerController { + constructor( + @repository(OrderRepository) + public orderRepository: OrderRepository, + ) { } + + @get('/orders/{id}/customer', { + responses: { + '200': { + description: 'Customer belonging to Order (resolved via Customer.name)', + content: { + 'application/json': { + schema: getModelSchemaRef(Customer), + }, + }, + }, + }, + }) + async getCustomer( + @param.path.number('id') id: typeof Order.prototype.id, + ): Promise { + return this.orderRepository.customer(id); + } +} + +`; + + +exports[`lb4 relation generates belongsTo relation with keyTo and custom relation name generated model includes both name and keyTo in belongsTo decorator 1`] = ` +import {Entity, model, property, belongsTo} from '@loopback/repository'; +import {Customer} from './customer.model'; + +@model() +export class Order extends Entity { + @property({ + type: 'number', + id: true, + default: 0, + }) + id?: number; + + @property({ + type: 'string', + }) + name?: string; + + @belongsTo(() => Customer, {name: 'myCustomer', keyTo: 'name'}) + customerId: string; + + constructor(data?: Partial) { + super(data); + } +} + +`; + + +exports[`lb4 relation generates belongsTo relation with keyTo and custom relation name generated controller description includes resolved via reference with custom relation name 1`] = ` +import { + repository, +} from '@loopback/repository'; +import { + param, + get, + getModelSchemaRef, +} from '@loopback/rest'; +import { + Order, + Customer, +} from '../models'; +import {OrderRepository} from '../repositories'; + +export class OrderCustomerController { + constructor( + @repository(OrderRepository) + public orderRepository: OrderRepository, + ) { } + + @get('/orders/{id}/customer', { + responses: { + '200': { + description: 'Customer belonging to Order (resolved via Customer.name)', + content: { + 'application/json': { + schema: getModelSchemaRef(Customer), + }, + }, + }, + }, + }) + async getCustomer( + @param.path.number('id') id: typeof Order.prototype.id, + ): Promise { + return this.orderRepository.myCustomer(id); + } +} + +`; diff --git a/packages/cli/test/integration/generators/relation.belongs-to.integration.js b/packages/cli/test/integration/generators/relation.belongs-to.integration.js index b3a89f045d71..04d36ec493cf 100644 --- a/packages/cli/test/integration/generators/relation.belongs-to.integration.js +++ b/packages/cli/test/integration/generators/relation.belongs-to.integration.js @@ -497,4 +497,81 @@ describe('lb4 relation', /** @this {Mocha.Suite} */ function () { }); }, ); + + context('generates belongsTo relation with keyTo option', () => { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(sandbox.path, () => + testUtils.givenLBProject(sandbox.path, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withArguments([ + '--config', + `{"relationType":"belongsTo","sourceModel":"Order","destinationModel":"Customer","foreignKeyName":"customerId","keyTo":"name"}`, + ]); + }); + + it('generated model includes keyTo in belongsTo decorator', async () => { + const sourceFilePath = path.join( + sandbox.path, + MODEL_APP_PATH, + sourceFileName, + ); + assert.file(sourceFilePath); + expectFileToMatchSnapshot(sourceFilePath); + }); + + it('generated controller description includes resolved via reference', async () => { + const filePath = path.join( + sandbox.path, + CONTROLLER_PATH, + controllerFileName, + ); + assert.file(filePath); + expectFileToMatchSnapshot(filePath); + }); + }); + + context( + 'generates belongsTo relation with keyTo and custom relation name', + () => { + before(async function runGeneratorWithAnswers() { + await sandbox.reset(); + await testUtils + .executeGenerator(generator) + .inDir(sandbox.path, () => + testUtils.givenLBProject(sandbox.path, { + additionalFiles: SANDBOX_FILES, + }), + ) + .withArguments([ + '--config', + `{"relationType":"belongsTo","sourceModel":"Order","destinationModel":"Customer","foreignKeyName":"customerId","relationName":"myCustomer","keyTo":"name"}`, + ]); + }); + + it('generated model includes both name and keyTo in belongsTo decorator', async () => { + const sourceFilePath = path.join( + sandbox.path, + MODEL_APP_PATH, + sourceFileName, + ); + assert.file(sourceFilePath); + expectFileToMatchSnapshot(sourceFilePath); + }); + + it('generated controller description includes resolved via reference with custom relation name', async () => { + const filePath = path.join( + sandbox.path, + CONTROLLER_PATH, + controllerFileName, + ); + assert.file(filePath); + expectFileToMatchSnapshot(filePath); + }); + }, + ); });