Skip to content

Commit 2297f06

Browse files
committed
fix: enable control update in case of multiple belongsTo relation
Signed-off-by: Muhammad Aaqil <aaqilniz@yahoo.com>
1 parent a999c26 commit 2297f06

7 files changed

Lines changed: 230 additions & 9 deletions

File tree

packages/cli/generators/relation/belongs-to-relation.generator.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,62 @@ module.exports = class BelongsToRelationGenerator extends (
6060
options.sourceModel + '-' + options.destinationModel;
6161
this.artifactInfo.outFile =
6262
utils.toFileName(this.artifactInfo.name) + '.controller.ts';
63-
6463
const dest = this.destinationPath(
6564
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
6665
);
67-
68-
this.copyTemplatedFiles(source, dest, this.artifactInfo);
69-
await relationUtils.addExportController(
70-
this,
71-
path.resolve(this.artifactInfo.outDir, 'index.ts'),
72-
this.artifactInfo.controllerClassName,
73-
utils.toFileName(this.artifactInfo.name) + '.controller',
74-
);
66+
if (this.fs.exists(dest)) {
67+
const project = new relationUtils.AstLoopBackProject();
68+
const sourceFile = project.addSourceFileAtPath(dest);
69+
const sourceClass = relationUtils.getClassObj(
70+
sourceFile,
71+
this.artifactInfo.controllerClassName,
72+
);
73+
const structure = sourceClass.getStructure();
74+
structure.methods.forEach((method, index) => {
75+
if (method.name.startsWith('get')) {
76+
const {statements, parameters} = method;
77+
const lastStatementIndex = statements.length - 1;
78+
let returnStatement = statements[lastStatementIndex];
79+
returnStatement = returnStatement.substring(
80+
returnStatement.indexOf('return') + 6,
81+
returnStatement.lastIndexOf(';'),
82+
);
83+
returnStatement = returnStatement.trim();
84+
85+
if (returnStatement.startsWith('[')) {
86+
returnStatement = returnStatement.substring(
87+
returnStatement.indexOf('[') + 1,
88+
returnStatement.lastIndexOf(']'),
89+
);
90+
}
91+
returnStatement = `${returnStatement},\n\t this.${this.artifactInfo.paramSourceRepository}.${this.artifactInfo.relationPropertyName}(${this.artifactInfo.sourceModelPrimaryKey})`;
92+
structure.methods[index].statements[
93+
lastStatementIndex
94+
] = `return [${returnStatement}];`;
95+
structure.methods[
96+
index
97+
].returnType = `Promise<Promise<${this.artifactInfo.targetModelClassName}>[]>`;
98+
parameters.forEach(({decorators}, paramIndex) => {
99+
decorators.forEach((decorator, decorIndex) => {
100+
structure.methods[index].parameters[paramIndex].decorators[
101+
decorIndex
102+
].name = `param.path.${decorator.name}`;
103+
});
104+
});
105+
}
106+
});
107+
sourceClass.set(structure);
108+
sourceClass.formatText();
109+
await sourceFile.save();
110+
} else {
111+
this.copyTemplatedFiles(source, dest, this.artifactInfo);
112+
await relationUtils.addExportController(
113+
this,
114+
path.resolve(this.artifactInfo.outDir, 'index.ts'),
115+
this.artifactInfo.controllerClassName,
116+
utils.toFileName(this.artifactInfo.name) + '.controller',
117+
);
118+
}
75119
}
76120

77121
async generateModels(options) {

packages/cli/snapshots/integration/generators/relation.belongs-to.integration.snapshots.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,92 @@ export * from './order-customer.controller';
183183
`;
184184

185185

186+
exports[`lb4 relation checks if the controller file created for multiple relations answers {"relationType":"belongsTo","sourceModel":"Task","destinationModel":"Employee","relationName":"assignedTo"} checks controller content with belongsTo relation for multiple relations 1`] = `
187+
import {
188+
repository,
189+
} from '@loopback/repository';
190+
import {
191+
param,
192+
get,
193+
getModelSchemaRef,
194+
} from '@loopback/rest';
195+
import {
196+
Task,
197+
Employee,
198+
} from '../models';
199+
import {TaskRepository} from '../repositories';
200+
201+
export class TaskEmployeeController {
202+
constructor(
203+
@repository(TaskRepository)
204+
public taskRepository: TaskRepository,
205+
) { }
206+
207+
@get('/tasks/{id}/employee', {
208+
responses: {
209+
'200': {
210+
description: 'Employee belonging to Task',
211+
content: {
212+
'application/json': {
213+
schema: getModelSchemaRef(Employee),
214+
},
215+
},
216+
},
217+
},
218+
})
219+
async getEmployee(
220+
@param.path.number('id') id: typeof Task.prototype.id,
221+
): Promise<Employee> {
222+
return this.taskRepository.assignedTo(id);
223+
}
224+
}
225+
226+
`;
227+
228+
229+
exports[`lb4 relation checks if the controller file created for multiple relations answers {"relationType":"belongsTo","sourceModel":"Task","destinationModel":"Employee","relationName":"createdBy"} checks controller content with belongsTo relation for multiple relations 1`] = `
230+
import {
231+
repository,
232+
} from '@loopback/repository';
233+
import {
234+
param,
235+
get,
236+
getModelSchemaRef,
237+
} from '@loopback/rest';
238+
import {
239+
Task,
240+
Employee,
241+
} from '../models';
242+
import {TaskRepository} from '../repositories';
243+
244+
export class TaskEmployeeController {
245+
constructor(
246+
@repository(TaskRepository)
247+
public taskRepository: TaskRepository,
248+
) { }
249+
250+
@get('/tasks/{id}/employee', {
251+
responses: {
252+
'200': {
253+
description: 'Employee belonging to Task',
254+
content: {
255+
'application/json': {
256+
schema: getModelSchemaRef(Employee),
257+
},
258+
},
259+
},
260+
},
261+
})
262+
async getEmployee(
263+
@param.path.number('id') id: typeof Task.prototype.id,
264+
): Promise<Employee> {
265+
return this.taskRepository.createdBy(id);
266+
}
267+
}
268+
269+
`;
270+
271+
186272
exports[`lb4 relation checks if the controller file created for same table relation answers {"relationType":"belongsTo","sourceModel":"Employee","destinationModel":"Employee"} checks controller content with belongsTo relation with same table 1`] = `
187273
export class EmployeeController {}
188274
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class TaskEmployeeController {}

packages/cli/test/fixtures/relation/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ exports.SANDBOX_FILES = [
234234
SourceEntries.PatientRepository,
235235
SourceEntries.AppointmentRepository,
236236
SourceEntries.EmployeeRepository,
237+
SourceEntries.TaskRepository,
237238

238239
SourceEntries.AccountModel,
239240
SourceEntries.CustomerModel,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {Entity, model, property} from '@loopback/repository';
2+
3+
@model()
4+
export class Task extends Entity {
5+
@property({
6+
type: 'number',
7+
id: true,
8+
default: 0,
9+
})
10+
id?: number;
11+
12+
@property({
13+
type: 'string',
14+
})
15+
title?: string;
16+
17+
constructor(data?: Partial<Task>) {
18+
super(data);
19+
}
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {inject} from '@loopback/core';
2+
import {BelongsToAccessor, DefaultCrudRepository} from '@loopback/repository';
3+
import {DbDataSource} from '../datasources';
4+
import {Customer, Task} from '../models';
5+
6+
export class TaskRepository extends DefaultCrudRepository<
7+
Task,
8+
typeof Task.prototype.id
9+
> {
10+
public readonly myCustomer: BelongsToAccessor<
11+
Customer,
12+
typeof Task.prototype.id
13+
>;
14+
constructor(@inject('datasources.db') dataSource: DbDataSource) {
15+
super(Task, dataSource);
16+
}
17+
}

packages/cli/test/integration/generators/relation.belongs-to.integration.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const sandbox = new TestSandbox(path.resolve(__dirname, '../.sandbox'));
2323
const sourceFileName = 'order.model.ts';
2424
const controllerFileName = 'order-customer.controller.ts';
2525
const controllerFileNameForSameTableRelation = 'employee.controller.ts';
26+
const controllerFileNameForMultipleRelations = 'task-employee.controller.ts';
2627
const repositoryFileName = 'order.repository.ts';
2728
const repositoryFileNameForSameTableRelation = 'employee.repository.ts';
2829
// speed up tests by avoiding reading docs
@@ -437,4 +438,55 @@ describe('lb4 relation', /** @this {Mocha.Suite} */ function () {
437438
);
438439
}
439440
});
441+
442+
context(
443+
'checks if the controller file created for multiple relations',
444+
() => {
445+
const promptArray = [
446+
{
447+
relationType: 'belongsTo',
448+
sourceModel: 'Task',
449+
destinationModel: 'Employee',
450+
relationName: 'createdBy',
451+
},
452+
{
453+
relationType: 'belongsTo',
454+
sourceModel: 'Task',
455+
destinationModel: 'Employee',
456+
relationName: 'assignedTo',
457+
},
458+
];
459+
460+
promptArray.forEach(function (multiItemPrompt) {
461+
describe('answers ' + JSON.stringify(multiItemPrompt), () => {
462+
suite(multiItemPrompt);
463+
});
464+
});
465+
466+
function suite(multiItemPrompt) {
467+
before(async function runGeneratorWithAnswers() {
468+
await sandbox.reset();
469+
await testUtils
470+
.executeGenerator(generator)
471+
.inDir(sandbox.path, () =>
472+
testUtils.givenLBProject(sandbox.path, {
473+
additionalFiles: SANDBOX_FILES,
474+
}),
475+
)
476+
.withOptions(options)
477+
.withPrompts(multiItemPrompt);
478+
});
479+
480+
it('checks controller content with belongsTo relation for multiple relations', async () => {
481+
const filePath = path.join(
482+
sandbox.path,
483+
CONTROLLER_PATH,
484+
controllerFileNameForMultipleRelations,
485+
);
486+
assert.file(filePath);
487+
expectFileToMatchSnapshot(filePath);
488+
});
489+
}
490+
},
491+
);
440492
});

0 commit comments

Comments
 (0)