-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(repository): add function findByForeignKeys
Implemented initial version of the new helper function findByForeignKeys that finds model instances that contain any of the provided foreign key values. Co-authored-by: Agnes Lin <[email protected]> Co-authored-by: Miroslav Bajtoš <[email protected]>
- Loading branch information
1 parent
0471315
commit 517c201
Showing
4 changed files
with
162 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
89 changes: 89 additions & 0 deletions
89
packages/repository/src/__tests__/unit/repositories/relation.helpers.unit.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright IBM Corp. 2019. 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 {expect} from '@loopback/testlab'; | ||
import {DefaultCrudRepository, findByForeignKeys, juggler} from '../../..'; | ||
import {model, property} from '../../../decorators'; | ||
import {Entity} from '../../../model'; | ||
|
||
describe('findByForeignKeys', () => { | ||
let productRepo: ProductRepository; | ||
|
||
before(() => { | ||
productRepo = new ProductRepository(testdb); | ||
}); | ||
|
||
beforeEach(async () => { | ||
await productRepo.deleteAll(); | ||
}); | ||
|
||
it('returns an empty array when no instances have the foreign key value', async () => { | ||
await productRepo.create({id: 1, name: 'product', categoryId: 1}); | ||
const products = await findByForeignKeys(productRepo, 'categoryId', [2]); | ||
expect(products).to.be.empty(); | ||
}); | ||
|
||
it('returns all instances that have the foreign key value', async () => { | ||
const pens = await productRepo.create({name: 'pens', categoryId: 1}); | ||
const pencils = await productRepo.create({name: 'pencils', categoryId: 1}); | ||
const products = await findByForeignKeys(productRepo, 'categoryId', [1]); | ||
expect(products).to.deepEqual([pens, pencils]); | ||
}); | ||
|
||
it('does not include instances with different foreign key values', async () => { | ||
const pens = await productRepo.create({name: 'pens', categoryId: 1}); | ||
const pencils = await productRepo.create({name: 'pencils', categoryId: 2}); | ||
const products = await findByForeignKeys(productRepo, 'categoryId', [1]); | ||
expect(products).to.deepEqual([pens]); | ||
expect(products).to.not.containDeep(pencils); | ||
}); | ||
|
||
it('returns all instances that have any of multiple foreign key values', async () => { | ||
const pens = await productRepo.create({name: 'pens', categoryId: 1}); | ||
const pencils = await productRepo.create({name: 'pencils', categoryId: 2}); | ||
const paper = await productRepo.create({name: 'paper', categoryId: 3}); | ||
const products = await findByForeignKeys(productRepo, 'categoryId', [1, 3]); | ||
expect(products).to.deepEqual([pens, paper]); | ||
expect(products).to.not.containDeep(pencils); | ||
}); | ||
|
||
it('throws error if scope is passed in and is non-empty', async () => { | ||
let errorMessage; | ||
try { | ||
await findByForeignKeys(productRepo, 'categoryId', [1], { | ||
limit: 1, | ||
}); | ||
} catch (error) { | ||
errorMessage = error.message; | ||
} | ||
expect(errorMessage).to.eql('scope is not supported'); | ||
}); | ||
|
||
/******************* HELPERS *******************/ | ||
|
||
@model() | ||
class Product extends Entity { | ||
@property({id: true}) | ||
id: number; | ||
@property() | ||
name: string; | ||
@property() | ||
categoryId: number; | ||
} | ||
|
||
class ProductRepository extends DefaultCrudRepository< | ||
Product, | ||
typeof Product.prototype.id | ||
> { | ||
constructor(dataSource: juggler.DataSource) { | ||
super(Product, dataSource); | ||
} | ||
} | ||
|
||
const testdb: juggler.DataSource = new juggler.DataSource({ | ||
name: 'db', | ||
connector: 'memory', | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright IBM Corp. 2019. 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 * as _ from 'lodash'; | ||
import {Entity, EntityCrudRepository, Filter, Options, Where} from '..'; | ||
|
||
/** | ||
* Finds model instances that contain any of the provided foreign key values. | ||
* | ||
* @param targetRepository - The target repository where the model instances are found | ||
* @param fkName - Name of the foreign key | ||
* @param fkValues - Array of the values of the foreign keys to be included | ||
* @param scope - Additional scope constraints (not currently supported) | ||
* @param options - Options for the operations | ||
*/ | ||
export async function findByForeignKeys< | ||
Target extends Entity, | ||
TargetID, | ||
TargetRelations extends object, | ||
ForeignKey | ||
>( | ||
targetRepository: EntityCrudRepository<Target, TargetID, TargetRelations>, | ||
fkName: StringKeyOf<Target>, | ||
fkValues: ForeignKey[], | ||
scope?: Filter<Target>, | ||
options?: Options, | ||
): Promise<(Target & TargetRelations)[]> { | ||
// throw error if scope is defined and non-empty | ||
// see https://github.com/strongloop/loopback-next/issues/3453 | ||
if (scope && !_.isEmpty(scope)) { | ||
throw new Error('scope is not supported'); | ||
} | ||
|
||
const where = ({ | ||
[fkName]: fkValues.length === 1 ? fkValues[0] : {inq: fkValues}, | ||
} as unknown) as Where<Target>; | ||
const targetFilter = {where}; | ||
|
||
return targetRepository.find(targetFilter, options); | ||
} | ||
|
||
export type StringKeyOf<T> = Extract<keyof T, string>; |