-
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.
- Loading branch information
Showing
7 changed files
with
363 additions
and
8 deletions.
There are no files selected for viewing
109 changes: 109 additions & 0 deletions
109
...itory/src/relations/has-and-belongs-to-many/has-and-belongs-to-many-repository.factory.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,109 @@ | ||
// Copyright IBM Corp. 2017,2018. All Rights Reserved. | ||
// Node module: @loopback/example-todo | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import * as debugFactory from 'debug'; | ||
import {camelCase} from 'lodash'; | ||
import {DataObject} from '../../common-types'; | ||
import {InvalidRelationError} from '../../errors'; | ||
import {Entity} from '../../model'; | ||
import {EntityCrudRepository} from '../../repositories/repository'; | ||
import {isTypeResolver} from '../../type-resolver'; | ||
import {Getter, HasAndBelongsToManyDefinition} from '../relation.types'; | ||
import { | ||
DefaultHasAndBelongsToManyRepository, | ||
HasAndBelongsToManyRepository, | ||
} from './has-and-belongs-to-many.repository'; | ||
|
||
const debug = debugFactory('loopback:repository:has-many-repository-factory'); | ||
|
||
export type HasAndBelongsToManyRepositoryFactory< | ||
Target extends Entity, | ||
ForeignKeyType | ||
> = (fkValue: ForeignKeyType) => HasAndBelongsToManyRepository<Target>; | ||
|
||
/** | ||
* Enforces a constraint on a repository based on a relationship contract | ||
* between models. For example, if a Customer model is related to an Order model | ||
* via a HasAndBelongsToMany relation, then, the relational repository returned by the | ||
* factory function would be constrained by a Customer model instance's id(s). | ||
* | ||
* @param relationMetadata The relation metadata used to describe the | ||
* relationship and determine how to apply the constraint. | ||
* @param targetRepositoryGetter The repository which represents the target model of a | ||
* relation attached to a datasource. | ||
* @returns The factory function which accepts a foreign key value to constrain | ||
* the given target repository | ||
*/ | ||
export function createHasAndBelongsToManyRepositoryFactory< | ||
Target extends Entity, | ||
TargetID, | ||
ForeignKeyType | ||
>( | ||
relationMetadata: HasAndBelongsToManyDefinition, | ||
targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>, | ||
): HasAndBelongsToManyRepositoryFactory<Target, ForeignKeyType> { | ||
const meta = resolveHasAndBelongsToManyMetadata(relationMetadata); | ||
debug('Resolved HasAndBelongsToMany relation metadata: %o', meta); | ||
return function(fkValue: ForeignKeyType) { | ||
// tslint:disable-next-line:no-any | ||
const constraint: any = {[meta.keyTo]: fkValue}; | ||
return new DefaultHasAndBelongsToManyRepository< | ||
Target, | ||
TargetID, | ||
EntityCrudRepository<Target, TargetID> | ||
>(targetRepositoryGetter, constraint as DataObject<Target>); | ||
}; | ||
} | ||
|
||
type HasAndBelongsToManyResolvedDefinition = HasAndBelongsToManyDefinition & { | ||
keyTo: string; | ||
}; | ||
|
||
/** | ||
* Resolves given hasMany metadata if target is specified to be a resolver. | ||
* Mainly used to infer what the `keyTo` property should be from the target's | ||
* belongsTo metadata | ||
* @param relationMeta hasMany metadata to resolve | ||
*/ | ||
function resolveHasAndBelongsToManyMetadata( | ||
relationMeta: HasAndBelongsToManyDefinition, | ||
): HasAndBelongsToManyResolvedDefinition { | ||
if (!isTypeResolver(relationMeta.target)) { | ||
const reason = 'target must be a type resolver'; | ||
throw new InvalidRelationError(reason, relationMeta); | ||
} | ||
|
||
if (relationMeta.keyTo) { | ||
// The explict cast is needed because of a limitation of type inference | ||
return relationMeta as HasAndBelongsToManyResolvedDefinition; | ||
} | ||
|
||
const sourceModel = relationMeta.source; | ||
if (!sourceModel || !sourceModel.modelName) { | ||
const reason = 'source model must be defined'; | ||
throw new InvalidRelationError(reason, relationMeta); | ||
} | ||
|
||
const targetModel = relationMeta.target(); | ||
debug( | ||
'Resolved model %s from given metadata: %o', | ||
targetModel.modelName, | ||
targetModel, | ||
); | ||
const defaultFkName = camelCase(sourceModel.modelName + '_id'); | ||
const hasDefaultFkProperty = | ||
targetModel.definition && | ||
targetModel.definition.properties && | ||
targetModel.definition.properties[defaultFkName]; | ||
|
||
if (!hasDefaultFkProperty) { | ||
const reason = `target model ${ | ||
targetModel.name | ||
} is missing definition of foreign key ${defaultFkName}`; | ||
throw new InvalidRelationError(reason, relationMeta); | ||
} | ||
|
||
return Object.assign(relationMeta, {keyTo: defaultFkName}); | ||
} |
37 changes: 37 additions & 0 deletions
37
...ges/repository/src/relations/has-and-belongs-to-many/has-and-belongs-to-many.decorator.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,37 @@ | ||
// Copyright IBM Corp. 2017. 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 {Entity, EntityResolver} from '../../model'; | ||
import {relation} from '../relation.decorator'; | ||
import {HasAndBelongsToManyDefinition, RelationType} from '../relation.types'; | ||
|
||
/** | ||
* Decorator for hasAndBelongsToMany | ||
* Calls property.array decorator underneath the hood and infers foreign key | ||
* name from target model name unless explicitly specified | ||
* @param targetResolver Target model for hasAndBelongsToMany relation | ||
* @param definition Optional metadata for setting up hasAndBelongsToMany relation | ||
* @returns {(target:any, key:string)} | ||
*/ | ||
export function hasAndBelongsToMany<T extends Entity>( | ||
targetResolver: EntityResolver<T>, | ||
definition?: Partial<HasAndBelongsToManyDefinition>, | ||
) { | ||
return function(decoratedTarget: Object, key: string) { | ||
const meta: HasAndBelongsToManyDefinition = Object.assign( | ||
// default values, can be customized by the caller | ||
{name: key}, | ||
// properties provided by the caller | ||
definition, | ||
// properties enforced by the decorator | ||
{ | ||
type: RelationType.hasAndBelongsToMany, | ||
source: decoratedTarget.constructor, | ||
target: targetResolver, | ||
}, | ||
); | ||
relation(meta)(decoratedTarget, key); | ||
}; | ||
} |
117 changes: 117 additions & 0 deletions
117
...es/repository/src/relations/has-and-belongs-to-many/has-and-belongs-to-many.repository.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,117 @@ | ||
// Copyright IBM Corp. 2017,2018. All Rights Reserved. | ||
// Node module: @loopback/example-todo | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
import {Getter} from '@loopback/context'; | ||
import {Count, DataObject, Options} from '../../common-types'; | ||
import {Entity} from '../../model'; | ||
import {Filter, Where} from '../../query'; | ||
import { | ||
constrainDataObject, | ||
constrainFilter, | ||
constrainWhere, | ||
} from '../../repositories/constraint-utils'; | ||
import {EntityCrudRepository} from '../../repositories/repository'; | ||
|
||
/** | ||
* CRUD operations for a target repository of a HasAndBelongsToMany relation | ||
*/ | ||
export interface HasAndBelongsToManyRepository<Target extends Entity> { | ||
/** | ||
* Create a target model instance | ||
* @param targetModelData The target model data | ||
* @param options Options for the operation | ||
* @returns A promise which resolves to the newly created target model instance | ||
*/ | ||
create( | ||
targetModelData: DataObject<Target>, | ||
options?: Options, | ||
): Promise<Target>; | ||
/** | ||
* Find target model instance(s) | ||
* @param filter A filter object for where, order, limit, etc. | ||
* @param options Options for the operation | ||
* @returns A promise which resolves with the found target instance(s) | ||
*/ | ||
find(filter?: Filter<Target>, options?: Options): Promise<Target[]>; | ||
/** | ||
* Delete multiple target model instances | ||
* @param where Instances within the where scope are deleted | ||
* @param options | ||
* @returns A promise which resolves the deleted target model instances | ||
*/ | ||
delete(where?: Where<Target>, options?: Options): Promise<Count>; | ||
/** | ||
* Patch multiple target model instances | ||
* @param dataObject The fields and their new values to patch | ||
* @param where Instances within the where scope are patched | ||
* @param options | ||
* @returns A promise which resolves the patched target model instances | ||
*/ | ||
patch( | ||
dataObject: DataObject<Target>, | ||
where?: Where<Target>, | ||
options?: Options, | ||
): Promise<Count>; | ||
} | ||
|
||
export class DefaultHasAndBelongsToManyRepository< | ||
TargetEntity extends Entity, | ||
TargetID, | ||
TargetRepository extends EntityCrudRepository<TargetEntity, TargetID> | ||
> implements HasAndBelongsToManyRepository<TargetEntity> { | ||
/** | ||
* Constructor of DefaultHasAndBelongsToManyEntityCrudRepository | ||
* @param getTargetRepository the getter of the related target model repository instance | ||
* @param constraint the key value pair representing foreign key name to constrain | ||
* the target repository instance | ||
*/ | ||
constructor( | ||
public getTargetRepository: Getter<TargetRepository>, | ||
public constraint: DataObject<TargetEntity>, | ||
) {} | ||
|
||
async create( | ||
targetModelData: DataObject<TargetEntity>, | ||
options?: Options, | ||
): Promise<TargetEntity> { | ||
const targetRepository = await this.getTargetRepository(); | ||
return targetRepository.create( | ||
constrainDataObject(targetModelData, this.constraint), | ||
options, | ||
); | ||
} | ||
|
||
async find( | ||
filter?: Filter<TargetEntity>, | ||
options?: Options, | ||
): Promise<TargetEntity[]> { | ||
const targetRepository = await this.getTargetRepository(); | ||
return targetRepository.find( | ||
constrainFilter(filter, this.constraint), | ||
options, | ||
); | ||
} | ||
|
||
async delete(where?: Where<TargetEntity>, options?: Options): Promise<Count> { | ||
const targetRepository = await this.getTargetRepository(); | ||
return targetRepository.deleteAll( | ||
constrainWhere(where, this.constraint as Where<TargetEntity>), | ||
options, | ||
); | ||
} | ||
|
||
async patch( | ||
dataObject: DataObject<TargetEntity>, | ||
where?: Where<TargetEntity>, | ||
options?: Options, | ||
): Promise<Count> { | ||
const targetRepository = await this.getTargetRepository(); | ||
return targetRepository.updateAll( | ||
constrainDataObject(dataObject, this.constraint), | ||
constrainWhere(where, this.constraint as Where<TargetEntity>), | ||
options, | ||
); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/repository/src/relations/has-and-belongs-to-many/index.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,8 @@ | ||
// Copyright IBM Corp. 2018. All Rights Reserved. | ||
// Node module: @loopback/repository | ||
// This file is licensed under the MIT License. | ||
// License text available at https://opensource.org/licenses/MIT | ||
|
||
export * from './has-and-belongs-to-many.decorator'; | ||
export * from './has-and-belongs-to-many.repository'; | ||
export * from './has-and-belongs-to-many-repository.factory'; |
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
Oops, something went wrong.