Skip to content

Commit 69cc478

Browse files
committed
fix: Take into account parent's primaryColumn config when fetching from polymorphic child
Before, on a child repository's options, we didn't handle the case in which there were multiple parents which had different `primaryColumn` names Now we take such configurations into account. Specifically, we fetch the metadata associated with the parent we are visiting and read its `primaryColumn`. This allows for multiple parents to have different ID names when such situations arise.
1 parent a3d806d commit 69cc478

File tree

4 files changed

+71
-13
lines changed

4 files changed

+71
-13
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Entity, PrimaryGeneratedColumn } from 'typeorm';
2+
import { PolymorphicChildren } from '../../../dist';
3+
import { AdvertEntity } from './advert.entity';
4+
5+
@Entity('admins')
6+
export class AdminEntity {
7+
@PrimaryGeneratedColumn()
8+
admin_id: number;
9+
10+
@PolymorphicChildren(() => AdvertEntity, {
11+
eager: false,
12+
primaryColumn: "admin_id"
13+
})
14+
adverts: AdvertEntity[];
15+
}

src/__tests__/entities/advert.entity.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
22
import { PolymorphicParent } from '../../../dist';
3+
import { AdminEntity } from './admin.entity';
34
import { MerchantEntity } from './merchant.entity';
45
import { UserEntity } from './user.entity';
56

@@ -8,10 +9,10 @@ export class AdvertEntity {
89
@PrimaryGeneratedColumn()
910
id: number;
1011

11-
@PolymorphicParent(() => [UserEntity, MerchantEntity], {
12+
@PolymorphicParent(() => [UserEntity, MerchantEntity, AdminEntity], {
1213
eager: true,
1314
})
14-
owner: UserEntity | MerchantEntity;
15+
owner: UserEntity | MerchantEntity | AdminEntity;
1516

1617
@Column({ nullable: true })
1718
entityId: number;

src/__tests__/polymorphic.repository.spec.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { DataSource, Repository } from 'typeorm';
2-
import { AdvertEntity } from './entities/advert.entity';
3-
import { UserEntity } from './entities/user.entity';
41
import { config } from 'dotenv';
52
import { resolve } from 'path';
6-
import { AdvertRepository } from './repository/advert.repository';
3+
import { DataSource } from 'typeorm';
74
import { AbstractPolymorphicRepository } from '../';
5+
import { AdminEntity } from './entities/admin.entity';
6+
import { AdvertEntity } from './entities/advert.entity';
87
import { MerchantEntity } from './entities/merchant.entity';
8+
import { UserEntity } from './entities/user.entity';
9+
import { AdvertRepository } from './repository/advert.repository';
910
import { UserRepository } from './repository/user.repository';
1011

1112
describe('AbstractPolymorphicRepository', () => {
@@ -22,7 +23,7 @@ describe('AbstractPolymorphicRepository', () => {
2223
port: parseInt(process.env.TYPEORM_PORT as string, 10),
2324
username: process.env.TYPEORM_USERNAME,
2425
password: process.env.TYPEORM_PASSWORD,
25-
entities: [UserEntity, AdvertEntity, MerchantEntity],
26+
entities: [UserEntity, AdvertEntity, MerchantEntity, AdminEntity],
2627
synchronize: process.env.TYPEORM_SYNCHRONIZE === 'true',
2728
database: process.env.TYPEORM_DATABASE,
2829
});
@@ -164,6 +165,31 @@ describe('AbstractPolymorphicRepository', () => {
164165
expect(result?.entityType).toBe(UserEntity.name);
165166
});
166167

168+
it('Can find entity with parent even with different primaryColumn', async () => {
169+
const repository: AbstractPolymorphicRepository<AdvertEntity> = AbstractPolymorphicRepository.createRepository(
170+
connection,
171+
AdvertRepository,
172+
);
173+
const adminRepository = connection.getRepository(AdminEntity);
174+
175+
const admin = await adminRepository.save(new AdminEntity());
176+
177+
const advert = await repository.save(
178+
repository.create({
179+
owner: admin,
180+
}),
181+
);
182+
console.log(advert);
183+
184+
const result = await repository.findOne({ where: { id: advert.id } });
185+
186+
expect(result).toBeInstanceOf(AdvertEntity);
187+
expect(result?.owner).toBeInstanceOf(AdminEntity);
188+
const owner = result?.owner as AdminEntity;
189+
expect(owner.admin_id).toBe(result?.entityId);
190+
expect(result?.entityType).toBe(AdminEntity.name);
191+
});
192+
167193
it('Can find entity without parent', async () => {
168194
const repository = AbstractPolymorphicRepository.createRepository(
169195
connection,
@@ -266,5 +292,8 @@ describe('AbstractPolymorphicRepository', () => {
266292
expect(result?.adverts[0].entityId).toBe(user.id);
267293
});
268294
});
295+
296+
269297
});
298+
270299
});

src/polymorphic.repository.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,25 @@ export abstract class AbstractPolymorphicRepository<
181181
options: PolymorphicMetadataInterface,
182182
): Promise<PolymorphicChildInterface[] | PolymorphicChildInterface | never> {
183183
const repository = this.findRepository(entityType);
184+
// If we are in the child repository, `options` has the child metadata
185+
// but not the parent's while options.type == 'parent'. But to
186+
// perform the query below we need the ID of the parent entity. Meaning
187+
// we need to read the `primaryColum` option from the parent repository.
188+
189+
// Act as if the parent repository is the current repository
190+
// and extract the metadata from it
191+
// FIXME: Come up with a nicer interface to do this
192+
const metadata: PolymorphicMetadataInterface[] = this.getPolymorphicMetadata.bind(repository)();
193+
194+
// Find the metadata associated to the current repository through `target`
195+
const found = metadata.find((m) => m.classType === this.target);
184196

185197
return repository[options.hasMany ? 'find' : 'findOne'](
186198
options.type === 'parent'
187199
? {
188200
where: {
189201
// TODO: Not sure about this change (key was just id before)
190-
[PrimaryColumn(options)]: parent[entityIdColumn(options)],
202+
[PrimaryColumn({ primaryColumn: found.primaryColumn, ...options })]: parent[entityIdColumn(options)],
191203
},
192204
}
193205
: {
@@ -266,14 +278,15 @@ export abstract class AbstractPolymorphicRepository<
266278
return entity;
267279
}
268280

269-
/**
270-
* Add parent's id and type to child's id and type field
271-
*/
281+
// FIXME: Come up with a nicer interface to do this
282+
const repository = this.findRepository(parent.constructor as Function);
283+
const parentMetadata: PolymorphicMetadataInterface[] = this.getPolymorphicMetadata.bind(repository)();
284+
const found = parentMetadata.find((m) => m.classType === this.target);
272285
type EntityKey = keyof DeepPartial<E>;
273286
entity[entityIdColumn(options) as EntityKey] =
274-
parent[PrimaryColumn(options)];
287+
parent[PrimaryColumn({ primaryColumn: found.primaryColumn, ...options })];
275288
entity[entityTypeColumn(options) as EntityKey] =
276-
parent.constructor.name;
289+
parent.constructor.name;
277290
return entity;
278291
});
279292
}

0 commit comments

Comments
 (0)