Skip to content

Commit

Permalink
feat(repository): add hasManyThrough factory and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Agnes Lin authored and agnes512 committed Jun 5, 2020
1 parent f0af355 commit 3304963
Show file tree
Hide file tree
Showing 4 changed files with 307 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/repository
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {createStubInstance, expect} from '@loopback/testlab';
import {
DefaultCrudRepository,
Entity,
Getter,
juggler,
model,
property,
} from '../../..';
import {createHasManyThroughRepositoryFactory} from '../../../relations/has-many/has-many-through-repository.factory';
import {HasManyThroughResolvedDefinition} from '../../../relations/has-many/has-many-through.helpers';

describe('createHasManyThroughRepositoryFactory', () => {
let categoryProductLinkRepo: CategoryProductLinkRepository;
let productRepo: ProductRepository;

beforeEach(() => {
givenStubbedProductRepo();
givenStubbedCategoryProductLinkRepo();
});

it('should return a function that could create hasManyThrough repository', () => {
const relationMeta = resolvedMetadata as HasManyThroughResolvedDefinition;
const result = createHasManyThroughRepositoryFactory(
relationMeta,
Getter.fromValue(productRepo),
Getter.fromValue(categoryProductLinkRepo),
);
expect(result).to.be.Function();
});

/*------------- HELPERS ---------------*/

@model()
class Category extends Entity {
@property({id: true})
id: number;

constructor(data: Partial<Category>) {
super(data);
}
}

@model()
class Product extends Entity {
@property({id: true})
id: number;

constructor(data: Partial<Product>) {
super(data);
}
}

@model()
class CategoryProductLink extends Entity {
@property({id: true})
id: number;
@property()
categoryId: number;
@property()
productId: number;

constructor(data: Partial<Product>) {
super(data);
}
}

class ProductRepository extends DefaultCrudRepository<
Product,
typeof Product.prototype.id
> {
constructor(dataSource: juggler.DataSource) {
super(Product, dataSource);
}
}

class CategoryProductLinkRepository extends DefaultCrudRepository<
CategoryProductLink,
typeof CategoryProductLink.prototype.id
> {
constructor(dataSource: juggler.DataSource) {
super(CategoryProductLink, dataSource);
}
}

const resolvedMetadata = {
name: 'products',
type: 'hasMany',
targetsMany: true,
source: Category,
keyFrom: 'id',
target: () => Product,
keyTo: 'id',
through: {
model: () => CategoryProductLink,
keyFrom: 'categoryId',
keyTo: 'productId',
},
};

function givenStubbedProductRepo() {
productRepo = createStubInstance(ProductRepository);
}

function givenStubbedCategoryProductLinkRepo() {
categoryProductLinkRepo = createStubInstance(CategoryProductLinkRepository);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/repository
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {
DataObject,
Entity,
EntityCrudRepository,
Getter,
HasManyDefinition,
} from '../..';
import {
createTargetConstraint,
createThroughConstraint,
createThroughFkConstraint,
resolveHasManyThroughMetadata,
} from './has-many-through.helpers';
import {
DefaultHasManyThroughRepository,
HasManyThroughRepository,
} from './has-many-through.repository';

/**
* a factory to generate hasManyThrough repository class.
*
* Warning: The hasManyThrough interface is experimental and is subject to change.
* If backwards-incompatible changes are made, a new major version may not be
* released.
*/

export type HasManyThroughRepositoryFactory<
TargetEntity extends Entity,
TargetID,
ThroughEntity extends Entity,
ForeignKeyType
> = (
fkValue: ForeignKeyType,
) => HasManyThroughRepository<TargetEntity, TargetID, ThroughEntity>;

export function createHasManyThroughRepositoryFactory<
Target extends Entity,
TargetID,
Through extends Entity,
ThroughID,
ForeignKeyType
>(
relationMetadata: HasManyDefinition,
targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
throughRepositoryGetter: Getter<EntityCrudRepository<Through, ThroughID>>,
): HasManyThroughRepositoryFactory<Target, TargetID, Through, ForeignKeyType> {
const meta = resolveHasManyThroughMetadata(relationMetadata);
const result = function (fkValue: ForeignKeyType) {
function getTargetContraint(
throughInstances: Through | Through[],
): DataObject<Target> {
return createTargetConstraint<Target, Through>(meta, throughInstances);
}
function getThroughConstraint(): DataObject<Through> {
const constriant: DataObject<Through> = createThroughConstraint<
Through,
ForeignKeyType
>(meta, fkValue);
return constriant;
}

function getThroughFkConstraint(
targetInstance: Target,
): DataObject<Through> {
const constriant: DataObject<Through> = createThroughFkConstraint<
Target,
Through
>(meta, targetInstance);
return constriant;
}

return new DefaultHasManyThroughRepository<
Target,
TargetID,
EntityCrudRepository<Target, TargetID>,
Through,
ThroughID,
EntityCrudRepository<Through, ThroughID>
>(
targetRepositoryGetter,
throughRepositoryGetter,
getTargetContraint,
getThroughConstraint,
getThroughFkConstraint,
);
};
return result;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Count, DataObject, Entity, Filter, Options, Where} from '../..';
import {
Count,
DataObject,
Entity,
EntityCrudRepository,
Filter,
Getter,
Options,
Where,
} from '../..';

/**
* CRUD operations for a target repository of a HasManyThrough relation
Expand All @@ -28,7 +37,7 @@ export interface HasManyThroughRepository<
throughData?: DataObject<Through>;
throughOptions?: Options;
},
): Promise<Target>;
): Promise<void>;

/**
* Find target model instance(s)
Expand All @@ -41,7 +50,7 @@ export interface HasManyThroughRepository<
options?: Options & {
throughOptions?: Options;
},
): Promise<Target[]>;
): Promise<void>;

/**
* Delete multiple target model instances
Expand Down Expand Up @@ -98,3 +107,88 @@ export interface HasManyThroughRepository<
},
): Promise<void>;
}

/**
* a class for CRUD operations for hasManyThrough relation.
*
* Warning: The hasManyThrough interface is experimental and is subject to change.
* If backwards-incompatible changes are made, a new major version may not be
* released.
*/
export class DefaultHasManyThroughRepository<
TargetEntity extends Entity,
TargetID,
TargetRepository extends EntityCrudRepository<TargetEntity, TargetID>,
ThroughEntity extends Entity,
ThroughID,
ThroughRepository extends EntityCrudRepository<ThroughEntity, ThroughID>
> implements HasManyThroughRepository<TargetEntity, TargetID, ThroughEntity> {
constructor(
public getTargetRepository: Getter<TargetRepository>,
public getThroughRepository: Getter<ThroughRepository>,
public getTargetConstraint: (
throughInstances: ThroughEntity | ThroughEntity[],
) => DataObject<TargetEntity>,
public getThroughConstraint: () => DataObject<ThroughEntity>,
public getThroughFkConstraint: (
targetInstance: TargetEntity,
) => DataObject<ThroughEntity>,
) {}

async create(
targetModelData: DataObject<TargetEntity>,
options?: Options & {
throughData?: DataObject<ThroughEntity>;
throughOptions?: Options;
},
): Promise<void> {
throw new Error('Method not implemented.');
}

async find(
filter?: Filter<TargetEntity>,
options?: Options & {
throughOptions?: Options;
},
): Promise<void> {
throw new Error('Method not implemented.');
}

async delete(
where?: Where<TargetEntity>,
options?: Options & {
throughOptions?: Options;
},
): Promise<Count> {
throw new Error('Method not implemented.');
}

async patch(
dataObject: DataObject<TargetEntity>,
where?: Where<TargetEntity>,
options?: Options & {
throughOptions?: Options;
},
): Promise<Count> {
throw new Error('Method not implemented.');
}

async link(
targetModelId: TargetID,
options?: Options & {
throughData?: DataObject<ThroughEntity>;
throughOptions?: Options;
},
): Promise<TargetEntity> {
throw new Error('Method not implemented.');
}

async unlink(
targetModelId: TargetID,
options?: Options & {
throughOptions?: Options;
},
): Promise<void> {
throw new Error('Method not implemented.');
}
}
6 changes: 4 additions & 2 deletions packages/repository/src/relations/has-many/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

export * from './has-many.decorator';
export * from './has-many.repository';
export * from './has-many-repository.factory';
export * from './has-many-through-repository.factory';
export * from './has-many-through.repository';
export * from './has-many.decorator';
export * from './has-many.inclusion-resolver';
export * from './has-many.repository';

0 comments on commit 3304963

Please sign in to comment.