-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(repository): add findByForeignKeys function #3473
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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', | ||
| }); | ||
| }); |
| 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[], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we accept a single value too? For example: fkValues: ForeignKey[] | ForeignKey,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to have a type param such as
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me reword it to make sure that I understand the type constrains in if we do
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm proposing the following: export async function findByForeignKeys<
Target extends Entity,
FK extends StringKeyOf<Target>,
TargetRelations extends object,
>(
targetRepository: EntityCrudRepository<Target, unknown, TargetRelations>,
fkName: FK,
fkValues: Target[FK][],
scope?: Filter<Target>,
options?: Options,
): Promise<(Target & TargetRelations)[]> {
} |
||
| 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>; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's not necessary to export this type? |
||
Uh oh!
There was an error while loading. Please reload this page.