Skip to content

Commit

Permalink
fix: Take into account parent's primaryColumn config when fetching fr…
Browse files Browse the repository at this point in the history
…om 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.
  • Loading branch information
danteBenitez committed Nov 27, 2024
1 parent a3d806d commit 69cc478
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 13 deletions.
15 changes: 15 additions & 0 deletions src/__tests__/entities/admin.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PolymorphicChildren } from '../../../dist';
import { AdvertEntity } from './advert.entity';

@Entity('admins')
export class AdminEntity {
@PrimaryGeneratedColumn()
admin_id: number;

@PolymorphicChildren(() => AdvertEntity, {
eager: false,
primaryColumn: "admin_id"
})
adverts: AdvertEntity[];
}
5 changes: 3 additions & 2 deletions src/__tests__/entities/advert.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PolymorphicParent } from '../../../dist';
import { AdminEntity } from './admin.entity';
import { MerchantEntity } from './merchant.entity';
import { UserEntity } from './user.entity';

Expand All @@ -8,10 +9,10 @@ export class AdvertEntity {
@PrimaryGeneratedColumn()
id: number;

@PolymorphicParent(() => [UserEntity, MerchantEntity], {
@PolymorphicParent(() => [UserEntity, MerchantEntity, AdminEntity], {
eager: true,
})
owner: UserEntity | MerchantEntity;
owner: UserEntity | MerchantEntity | AdminEntity;

@Column({ nullable: true })
entityId: number;
Expand Down
39 changes: 34 additions & 5 deletions src/__tests__/polymorphic.repository.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DataSource, Repository } from 'typeorm';
import { AdvertEntity } from './entities/advert.entity';
import { UserEntity } from './entities/user.entity';
import { config } from 'dotenv';
import { resolve } from 'path';
import { AdvertRepository } from './repository/advert.repository';
import { DataSource } from 'typeorm';
import { AbstractPolymorphicRepository } from '../';
import { AdminEntity } from './entities/admin.entity';
import { AdvertEntity } from './entities/advert.entity';
import { MerchantEntity } from './entities/merchant.entity';
import { UserEntity } from './entities/user.entity';
import { AdvertRepository } from './repository/advert.repository';
import { UserRepository } from './repository/user.repository';

describe('AbstractPolymorphicRepository', () => {
Expand All @@ -22,7 +23,7 @@ describe('AbstractPolymorphicRepository', () => {
port: parseInt(process.env.TYPEORM_PORT as string, 10),
username: process.env.TYPEORM_USERNAME,
password: process.env.TYPEORM_PASSWORD,
entities: [UserEntity, AdvertEntity, MerchantEntity],
entities: [UserEntity, AdvertEntity, MerchantEntity, AdminEntity],
synchronize: process.env.TYPEORM_SYNCHRONIZE === 'true',
database: process.env.TYPEORM_DATABASE,
});
Expand Down Expand Up @@ -164,6 +165,31 @@ describe('AbstractPolymorphicRepository', () => {
expect(result?.entityType).toBe(UserEntity.name);
});

it('Can find entity with parent even with different primaryColumn', async () => {
const repository: AbstractPolymorphicRepository<AdvertEntity> = AbstractPolymorphicRepository.createRepository(
connection,
AdvertRepository,
);
const adminRepository = connection.getRepository(AdminEntity);

const admin = await adminRepository.save(new AdminEntity());

const advert = await repository.save(
repository.create({
owner: admin,
}),
);
console.log(advert);

const result = await repository.findOne({ where: { id: advert.id } });

expect(result).toBeInstanceOf(AdvertEntity);
expect(result?.owner).toBeInstanceOf(AdminEntity);
const owner = result?.owner as AdminEntity;
expect(owner.admin_id).toBe(result?.entityId);
expect(result?.entityType).toBe(AdminEntity.name);
});

it('Can find entity without parent', async () => {
const repository = AbstractPolymorphicRepository.createRepository(
connection,
Expand Down Expand Up @@ -266,5 +292,8 @@ describe('AbstractPolymorphicRepository', () => {
expect(result?.adverts[0].entityId).toBe(user.id);
});
});


});

});
25 changes: 19 additions & 6 deletions src/polymorphic.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,25 @@ export abstract class AbstractPolymorphicRepository<
options: PolymorphicMetadataInterface,
): Promise<PolymorphicChildInterface[] | PolymorphicChildInterface | never> {
const repository = this.findRepository(entityType);
// If we are in the child repository, `options` has the child metadata
// but not the parent's while options.type == 'parent'. But to
// perform the query below we need the ID of the parent entity. Meaning
// we need to read the `primaryColum` option from the parent repository.

// Act as if the parent repository is the current repository
// and extract the metadata from it
// FIXME: Come up with a nicer interface to do this
const metadata: PolymorphicMetadataInterface[] = this.getPolymorphicMetadata.bind(repository)();

// Find the metadata associated to the current repository through `target`
const found = metadata.find((m) => m.classType === this.target);

return repository[options.hasMany ? 'find' : 'findOne'](
options.type === 'parent'
? {
where: {
// TODO: Not sure about this change (key was just id before)
[PrimaryColumn(options)]: parent[entityIdColumn(options)],
[PrimaryColumn({ primaryColumn: found.primaryColumn, ...options })]: parent[entityIdColumn(options)],
},
}
: {
Expand Down Expand Up @@ -266,14 +278,15 @@ export abstract class AbstractPolymorphicRepository<
return entity;
}

/**
* Add parent's id and type to child's id and type field
*/
// FIXME: Come up with a nicer interface to do this
const repository = this.findRepository(parent.constructor as Function);
const parentMetadata: PolymorphicMetadataInterface[] = this.getPolymorphicMetadata.bind(repository)();
const found = parentMetadata.find((m) => m.classType === this.target);
type EntityKey = keyof DeepPartial<E>;
entity[entityIdColumn(options) as EntityKey] =
parent[PrimaryColumn(options)];
parent[PrimaryColumn({ primaryColumn: found.primaryColumn, ...options })];
entity[entityTypeColumn(options) as EntityKey] =
parent.constructor.name;
parent.constructor.name;
return entity;
});
}
Expand Down

0 comments on commit 69cc478

Please sign in to comment.