Skip to content

Commit

Permalink
feat(repository): add more helpers for HasManyThrough
Browse files Browse the repository at this point in the history
  • Loading branch information
Agnes Lin committed Jun 3, 2020
1 parent c764ac6 commit 4e85e21
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Copyright IBM Corp. 2019,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
Expand All @@ -9,6 +9,7 @@ import {
constrainDataObjects,
constrainFilter,
constrainWhere,
constrainWhereOr,
Entity,
Filter,
FilterBuilder,
Expand Down Expand Up @@ -77,6 +78,23 @@ describe('constraint utility functions', () => {
});
});

context('constrainWhereOr', () => {
const inputWhere: Where<{x: string; y: string; id: string}> = {
x: 'x',
};
it('enforces a constraint', () => {
const constraint = [{id: '5'}, {y: 'y'}];
const result = constrainWhereOr(inputWhere, constraint);
expect(result).to.deepEqual({...inputWhere, or: constraint});
});

it('enforces constraint with dup key', () => {
const constraint = [{y: 'z'}, {x: 'z'}];
const result = constrainWhereOr(inputWhere, constraint);
expect(result).to.deepEqual({...inputWhere, or: constraint});
});
});

context('constrainDataObject', () => {
it('constrains a single data object', () => {
const input = new Order({description: 'order 1'});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import {
createTargetConstraint,
createThroughConstraint,
createThroughFkConstraint,
HasManyThroughResolvedDefinition,
resolveHasManyThroughMetadata,
} from '../../../../relations/has-many/has-many-through.helpers';
Expand Down Expand Up @@ -42,12 +43,40 @@ describe('HasManyThroughHelpers', () => {
const resolved = resolvedMetadata as HasManyThroughResolvedDefinition;

// single through model
let result = createTargetConstraint(resolved, [through1]);
let result = createTargetConstraint(resolved, through1);
expect(result).to.containEql({id: 9});
// multiple through models
result = createTargetConstraint(resolved, [through1, through2]);
expect(result).to.containEql({id: {inq: [9, 8]}});
});

it('can create constraint for searching target models with duplicate keys', () => {
const through1 = createCategoryProductLink({
id: 1,
categoryId: 2,
productId: 9,
});
const through2 = createCategoryProductLink({
id: 2,
categoryId: 3,
productId: 9,
});
const resolved = resolvedMetadata as HasManyThroughResolvedDefinition;

const result = createTargetConstraint(resolved, [through1, through2]);
expect(result).to.containEql({id: 9});
});
});
context('createThroughFkConstraint', () => {
it('can create constraint with a given target instance', () => {
const product = createProduct({
id: 1,
});
const resolved = resolvedMetadata as HasManyThroughResolvedDefinition;

const result = createThroughFkConstraint(resolved, product);
expect(result).to.containEql({productId: 1});
});
});
context('resolveHasManyThroughMetadata', () => {
it('throws if the wrong metadata type is used', async () => {
Expand Down Expand Up @@ -324,4 +353,7 @@ describe('HasManyThroughHelpers', () => {
function createCategoryProductLink(properties: Partial<CategoryProductLink>) {
return new CategoryProductLink(properties);
}
function createProduct(properties: Partial<Product>) {
return new Product(properties);
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import debugFactory from 'debug';
import {camelCase} from 'lodash';
import {
DataObject,
deduplicate,
Entity,
HasManyDefinition,
InvalidRelationError,
isTypeResolver,
StringKeyOf,
} from '../..';
import {resolveHasManyMetaHelper} from './has-many.helpers';

Expand All @@ -27,7 +29,7 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {

/**
* Creates constraint used to query target
* @param relationMeta - hasManyThrough metadata to resolve
* @param relationMeta - resolved hasManyThrough metadata
* @param throughInstances - Instances of through entities used to constrain the target
* @internal
*
Expand All @@ -50,26 +52,32 @@ export type HasManyThroughResolvedDefinition = HasManyDefinition & {
categoryId: 2,
productId: 8,
}, {
id: 2,
id: 1,
categoryId: 2,
productId: 9,
}
]);
>>> {id: {inq: [9, 8]}}
* ```
*/
export function createTargetConstraint<
Target extends Entity,
Through extends Entity
>(
relationMeta: HasManyThroughResolvedDefinition,
throughInstances: Through[],
throughInstances: Through | Through[],
): DataObject<Target> {
const targetPrimaryKey = relationMeta.keyTo;
const targetFkName = relationMeta.through.keyTo;
const fkValues = throughInstances.map(
if (!Array.isArray(throughInstances)) {
throughInstances = [throughInstances];
}
let fkValues = throughInstances.map(
(throughInstance: Through) =>
throughInstance[targetFkName as keyof Through],
);
fkValues = deduplicate(fkValues);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const constraint: any = {
[targetPrimaryKey]: fkValues.length === 1 ? fkValues[0] : {inq: fkValues},
Expand All @@ -80,7 +88,7 @@ export function createTargetConstraint<
/**
* Creates constraint used to query through model
*
* @param relationMeta - hasManyThrough metadata to resolve
* @param relationMeta - resolved hasManyThrough metadata
* @param fkValue - Value of the foreign key of the source model used to constrain through
* @param targetInstance - Instance of target entity used to constrain through
* @internal
Expand All @@ -98,6 +106,8 @@ export function createTargetConstraint<
* },
* };
* createThroughConstraint(resolvedMetadata, 1);
*
* >>> {categoryId: 1}
* ```
*/
export function createThroughConstraint<Through extends Entity, ForeignKeyType>(
Expand All @@ -109,6 +119,45 @@ export function createThroughConstraint<Through extends Entity, ForeignKeyType>(
const constraint: any = {[sourceFkName]: fkValue};
return constraint;
}
/**
* Creates constraint used to create the through model
*
* @param relationMeta - resolved hasManyThrough metadata
* @param targetInstance instance of target entity used to constrain through
* @internal
*
* @example
* ```ts
* const resolvedMetadata = {
* // .. other props
* keyFrom: 'id',
* keyTo: 'id',
* through: {
* model: () => CategoryProductLink,
* keyFrom: 'categoryId',
* keyTo: 'productId',
* },
* };
* createThroughConstraint(resolvedMetadata, {id: 3, name: 'a product'});
*
* >>> {productId: 1}
*
* createThroughConstraint(resolvedMetadata, {id: {inq:[3,4]}});
*
* >>> {productId: {inq:[3,4]}}
*/
export function createThroughFkConstraint<Target, Through extends Entity>(
relationMeta: HasManyThroughResolvedDefinition,
targetInstance: Target,
): DataObject<Through> {
const targetKey = relationMeta.keyTo as StringKeyOf<Target>;
const targetFkName = relationMeta.through.keyTo;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const constraint: any = {
[targetFkName]: targetInstance[targetKey],
};
return constraint;
}

/**
* Resolves given hasMany metadata if target is specified to be a resolver.
Expand Down
17 changes: 17 additions & 0 deletions packages/repository/src/repositories/constraint-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ export function constrainWhere<T extends object>(
const builder = new WhereBuilder<T>(where);
return builder.impose(constraint).build();
}
/**
* A utility function which takes a where filter and enforces constraint(s)
* on it with OR clause
* @param originalWhere - the where filter to apply the constrain(s) to
* @param constraint - the constraint which is to be applied on the filter with
* or clause
* @returns Filter the modified filter with the constraint, otherwise
* the original filter
*/
export function constrainWhereOr<T extends object>(
originalWhere: Where<T> | undefined,
constraint: Where<T>[],
): Where<T> {
const where = cloneDeep(originalWhere);
const builder = new WhereBuilder<T>(where);
return builder.or(constraint).build();
}
/**
* A utility function which takes a model instance data and enforces constraint(s)
* on it
Expand Down

0 comments on commit 4e85e21

Please sign in to comment.