Skip to content

Commit 3abecb8

Browse files
committed
fix: enable control update in case of multiple belongsTo relation
Signed-off-by: Muhammad Aaqil <[email protected]>
1 parent 0c7708d commit 3abecb8

File tree

7 files changed

+227
-7
lines changed

7 files changed

+227
-7
lines changed

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

+53-7
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,59 @@ module.exports = class BelongsToRelationGenerator extends (
6565
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
6666
);
6767

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-
);
68+
if (this.fs.exists(dest)) {
69+
const project = new relationUtils.AstLoopBackProject();
70+
const sourceFile = project.addSourceFileAtPath(dest);
71+
const sourceClass = relationUtils.getClassObj(
72+
sourceFile,
73+
this.artifactInfo.controllerClassName,
74+
);
75+
const structure = sourceClass.getStructure();
76+
structure.methods.forEach((method, index) => {
77+
if (method.name.startsWith('get')) {
78+
const {statements, parameters} = method;
79+
const lastStatementIndex = statements.length - 1;
80+
let returnStatement = statements[lastStatementIndex];
81+
returnStatement = returnStatement.substring(
82+
returnStatement.indexOf('return') + 6,
83+
returnStatement.lastIndexOf(';'),
84+
);
85+
returnStatement = returnStatement.trim();
86+
87+
if (returnStatement.startsWith('[')) {
88+
returnStatement = returnStatement.substring(
89+
returnStatement.indexOf('[') + 1,
90+
returnStatement.lastIndexOf(']'),
91+
);
92+
}
93+
returnStatement = `${returnStatement},\n\t this.${this.artifactInfo.paramSourceRepository}.${this.artifactInfo.relationPropertyName}(${this.artifactInfo.sourceModelPrimaryKey})`;
94+
structure.methods[index].statements[
95+
lastStatementIndex
96+
] = `return [${returnStatement}];`;
97+
structure.methods[
98+
index
99+
].returnType = `Promise<Promise<${this.artifactInfo.targetModelClassName}>[]>`;
100+
parameters.forEach(({decorators}, paramIndex) => {
101+
decorators.forEach((decorator, decorIndex) => {
102+
structure.methods[index].parameters[paramIndex].decorators[
103+
decorIndex
104+
].name = `param.path.${decorator.name}`;
105+
});
106+
});
107+
}
108+
});
109+
sourceClass.set(structure);
110+
sourceClass.formatText();
111+
await sourceFile.save();
112+
} else {
113+
this.copyTemplatedFiles(source, dest, this.artifactInfo);
114+
await relationUtils.addExportController(
115+
this,
116+
path.resolve(this.artifactInfo.outDir, 'index.ts'),
117+
this.artifactInfo.controllerClassName,
118+
utils.toFileName(this.artifactInfo.name) + '.controller',
119+
);
120+
}
75121
}
76122

77123
async generateModels(options) {

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

+86
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,92 @@ export * from './order-customer.controller';
238238
`;
239239

240240

241+
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`] = `
242+
import {
243+
repository,
244+
} from '@loopback/repository';
245+
import {
246+
param,
247+
get,
248+
getModelSchemaRef,
249+
} from '@loopback/rest';
250+
import {
251+
Task,
252+
Employee,
253+
} from '../models';
254+
import {TaskRepository} from '../repositories';
255+
256+
export class TaskEmployeeController {
257+
constructor(
258+
@repository(TaskRepository)
259+
public taskRepository: TaskRepository,
260+
) { }
261+
262+
@get('/tasks/{id}/employee', {
263+
responses: {
264+
'200': {
265+
description: 'Employee belonging to Task',
266+
content: {
267+
'application/json': {
268+
schema: getModelSchemaRef(Employee),
269+
},
270+
},
271+
},
272+
},
273+
})
274+
async getEmployee(
275+
@param.path.number('id') id: typeof Task.prototype.id,
276+
): Promise<Employee> {
277+
return this.taskRepository.assignedTo(id);
278+
}
279+
}
280+
281+
`;
282+
283+
284+
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`] = `
285+
import {
286+
repository,
287+
} from '@loopback/repository';
288+
import {
289+
param,
290+
get,
291+
getModelSchemaRef,
292+
} from '@loopback/rest';
293+
import {
294+
Task,
295+
Employee,
296+
} from '../models';
297+
import {TaskRepository} from '../repositories';
298+
299+
export class TaskEmployeeController {
300+
constructor(
301+
@repository(TaskRepository)
302+
public taskRepository: TaskRepository,
303+
) { }
304+
305+
@get('/tasks/{id}/employee', {
306+
responses: {
307+
'200': {
308+
description: 'Employee belonging to Task',
309+
content: {
310+
'application/json': {
311+
schema: getModelSchemaRef(Employee),
312+
},
313+
},
314+
},
315+
},
316+
})
317+
async getEmployee(
318+
@param.path.number('id') id: typeof Task.prototype.id,
319+
): Promise<Employee> {
320+
return this.taskRepository.createdBy(id);
321+
}
322+
}
323+
324+
`;
325+
326+
241327
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`] = `
242328
export class EmployeeController {}
243329
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export class TaskEmployeeController {}

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

+1
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,
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+
}
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

+49
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
@@ -497,4 +498,52 @@ describe('lb4 relation', /** @this {Mocha.Suite} */ function () {
497498
});
498499
},
499500
);
501+
502+
context(
503+
'checks if the controller file created for multiple relations',
504+
() => {
505+
const promptArray = [
506+
{
507+
relationType: 'belongsTo',
508+
sourceModel: 'Task',
509+
destinationModel: 'Employee',
510+
relationName: 'createdBy',
511+
},
512+
{
513+
relationType: 'belongsTo',
514+
sourceModel: 'Task',
515+
destinationModel: 'Employee',
516+
relationName: 'assignedTo',
517+
},
518+
];
519+
promptArray.forEach(function (multiItemPrompt) {
520+
describe('answers ' + JSON.stringify(multiItemPrompt), () => {
521+
suite(multiItemPrompt);
522+
});
523+
});
524+
function suite(multiItemPrompt) {
525+
before(async function runGeneratorWithAnswers() {
526+
await sandbox.reset();
527+
await testUtils
528+
.executeGenerator(generator)
529+
.inDir(sandbox.path, () =>
530+
testUtils.givenLBProject(sandbox.path, {
531+
additionalFiles: SANDBOX_FILES,
532+
}),
533+
)
534+
.withOptions(options)
535+
.withPrompts(multiItemPrompt);
536+
});
537+
it('checks controller content with belongsTo relation for multiple relations', async () => {
538+
const filePath = path.join(
539+
sandbox.path,
540+
CONTROLLER_PATH,
541+
controllerFileNameForMultipleRelations,
542+
);
543+
assert.file(filePath);
544+
expectFileToMatchSnapshot(filePath);
545+
});
546+
}
547+
},
548+
);
500549
});

0 commit comments

Comments
 (0)