From ff03493fcec6e89085814784ced8295dc4413420 Mon Sep 17 00:00:00 2001 From: Agnes Lin Date: Fri, 13 Sep 2019 16:20:25 -0400 Subject: [PATCH] docs: modify docs --- docs/site/HasMany-relation.md | 27 +--- .../repositories/customer.repository.ts | 4 +- ...-inclusion-resolver.factory.integration.ts | 66 --------- .../build-lookup-map.helpers.ts | 129 ------------------ .../find-by-foreign-keys.helpers.unit.ts | 90 ------------ ...s-of-one-to-many-relation-helpers.unit.ts} | 8 +- ...targets-of-one-to-many-relation.helpers.ts | 71 ---------- .../relations-helpers-fixtures.ts | 80 ++--------- .../src/relations/relation.helpers.ts | 6 +- 9 files changed, 22 insertions(+), 459 deletions(-) delete mode 100644 packages/repository/src/__tests__/integration/repositories/has-many-inclusion-resolver.factory.integration.ts delete mode 100644 packages/repository/src/__tests__/unit/repositories/relations-helpers/build-lookup-map.helpers.ts delete mode 100644 packages/repository/src/__tests__/unit/repositories/relations-helpers/find-by-foreign-keys.helpers.unit.ts rename packages/repository/src/__tests__/unit/repositories/relations-helpers/{flatten-targets-of-one-to-many-relation-helpers.ts => flatten-targets-of-one-to-many-relation-helpers.unit.ts} (79%) delete mode 100644 packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation.helpers.ts diff --git a/docs/site/HasMany-relation.md b/docs/site/HasMany-relation.md index 4fef85a31ac9..3da5c5b91c30 100644 --- a/docs/site/HasMany-relation.md +++ b/docs/site/HasMany-relation.md @@ -322,31 +322,8 @@ to query data through an `include` filter. An inclusion resolver is a function that can fetch target models for the given list of source model instances. LoopBack 4 creates a different inclusion resolver for each relation type. -The following is an example for a HasMany inclusion resolver: - -- Two models: `Customer` and `Order` -- A `Customer` has many `Order`s - -```ts -// import statements -class Customer extends Entity { - // property definition for id, name - @hasMany(() => Order, {keyTo: 'customerId'}) - orders?: Order[]; -} -``` - -```ts -// import statements -class Order extends Entity { - // property definition for id, name - @property({ - type: 'number', - }) - customerId?: number; - // constructor, relation, etc -} -``` +Use the relation between `Customer` and `Order` we show above, a `Customer` has +many `Order`s. After setting up the relation in the repository class, the inclusion resolver allows users to retrieve all customers along with their related orders through diff --git a/packages/repository-tests/src/crud/relations/fixtures/repositories/customer.repository.ts b/packages/repository-tests/src/crud/relations/fixtures/repositories/customer.repository.ts index d5e204c17708..9de711d82756 100644 --- a/packages/repository-tests/src/crud/relations/fixtures/repositories/customer.repository.ts +++ b/packages/repository-tests/src/crud/relations/fixtures/repositories/customer.repository.ts @@ -49,10 +49,8 @@ export function createCustomerRepo(repoClass: CrudRepositoryCtor) { addressRepositoryGetter: Getter, ) { super(Customer, db); - // create a has-many relation from this public method - // if the class extends from DefaultCrud, it can use getRelationDefinition() to - // check and get valid mata from entities. const ordersMeta = this.entityClass.definition.relations['orders']; + // create a has-many relation through this public method this.orders = createHasManyRepositoryFactory( ordersMeta as HasManyDefinition, orderRepositoryGetter, diff --git a/packages/repository/src/__tests__/integration/repositories/has-many-inclusion-resolver.factory.integration.ts b/packages/repository/src/__tests__/integration/repositories/has-many-inclusion-resolver.factory.integration.ts deleted file mode 100644 index c58eb9c5231e..000000000000 --- a/packages/repository/src/__tests__/integration/repositories/has-many-inclusion-resolver.factory.integration.ts +++ /dev/null @@ -1,66 +0,0 @@ - // put this test back to repository/factory... cuz for some reason it says createHasManyInclusionResolver is not a function - - // describe.only('HasMany inclusion resolvers - integration', () =>{ - // before(deleteAllModelsInDefaultDataSource); - // let categoryRepo: EntityCrudRepository< - // Category, - // typeof Category.prototype.id, - // CategoryRelations - // >; - // let productRepo: EntityCrudRepository< - // Product, - // typeof Product.prototype.id, - // ProductRelations - // >; - // before( - // withCrudCtx(async function setupRepository(ctx: CrudTestContext) { - // // this repo doesn't have registered resolvers yet - // categoryRepo = new repositoryClass(Category, ctx.dataSource); - // productRepo = new repositoryClass(Product, ctx.dataSource); - - // // inclusionResolvers should be defined, and no resolvers setup yet - // expect(categoryRepo.inclusionResolvers).to.not.be.undefined(); - // expect(categoryRepo.inclusionResolvers).to.deepEqual(new Map()); - // expect(productRepo.inclusionResolvers).to.not.be.undefined(); - - // console.log(productRepo); - // const productMeta = Category.definition.relations.products; - // const hasManyProductsResolver: InclusionResolver = createHasManyInclusionResolver( - // productMeta as HasManyDefinition, - // async () => productRepo, - // ); - // categoryRepo.inclusionResolvers.set('products', hasManyProductsResolver); - - // await ctx.dataSource.automigrate([Category.name, Product.name]); - // }), - // ); - - // beforeEach(async () => { - // await categoryRepo.deleteAll(); - // await productRepo.deleteAll(); - // }); - // it.only('ignores navigational properties when updating model instance', async () => { - // const created = await categoryRepo.create({name: 'Stationery'}); - // const categoryId = created.id; - - // await productRepo.create({ - // name: 'Pen', - // categoryId, - // }); - - // const found = await categoryRepo.findById(categoryId, { - // include: [{relation: 'items'}], - // }); - // expect(found.products).to.have.lengthOf(1); - - // found.name = 'updated name'; - // const saved = await categoryRepo.save(found); - // expect(saved.name).to.equal('updated name'); - - // const loaded = await categoryRepo.findById(categoryId); - // expect(loaded.name).to.equal('updated name'); - // expect(loaded).to.not.have.property('items'); - // }); - - - // }); \ No newline at end of file diff --git a/packages/repository/src/__tests__/unit/repositories/relations-helpers/build-lookup-map.helpers.ts b/packages/repository/src/__tests__/unit/repositories/relations-helpers/build-lookup-map.helpers.ts deleted file mode 100644 index 16592be6336a..000000000000 --- a/packages/repository/src/__tests__/unit/repositories/relations-helpers/build-lookup-map.helpers.ts +++ /dev/null @@ -1,129 +0,0 @@ -// 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 { - buildLookupMap, - findByForeignKeys, - reduceAsArray, - reduceAsSingleItem, -} from '../../../..'; -import { - Category, - CategoryRepository, - Product, - ProductRepository, - testdb, -} from './relations-helpers-fixtures'; - -describe('buildLoopupMap', () => { - let productRepo: ProductRepository; - let categoryRepo: CategoryRepository; - - before(() => { - productRepo = new ProductRepository(testdb); - categoryRepo = new CategoryRepository(testdb, async () => productRepo); - }); - - beforeEach(async () => { - await productRepo.deleteAll(); - await categoryRepo.deleteAll(); - }); - describe('get the result of using reduceAsArray strategy', async () => { - it('returns multiple instances in an array', async () => { - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - const pencils = await productRepo.create({ - name: 'pencils', - categoryId: 1, - }); - await productRepo.create({name: 'eraser', categoryId: 2}); - const products = await findByForeignKeys(productRepo, 'categoryId', 1); - - const result = buildLookupMap( - products, - 'categoryId', - reduceAsArray, - ); - const expected = new Map>(); - expected.set(1, [pens, pencils]); - expect(result).to.eql(expected); - }); - it('return instances in multiple arrays', async () => { - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - const pencils = await productRepo.create({ - name: 'pencils', - categoryId: 1, - }); - const erasers = await productRepo.create({name: 'eraser', categoryId: 2}); - const products = await findByForeignKeys(productRepo, 'categoryId', [ - 1, - 2, - ]); - - const result = buildLookupMap( - products, - 'categoryId', - reduceAsArray, - ); - const expected = new Map>(); - expected.set(1, [pens, pencils]); - expected.set(2, [erasers]); - expect(result).to.eql(expected); - }); - }); - describe('get the result of using reduceAsSingleItem strategy', async () => { - it('returns one instance when one target instance is passed in', async () => { - const cat = await categoryRepo.create({id: 1, name: 'angus'}); - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - //const pencils = await productRepo.create({name: 'pencils', categoryId: 1}); - await productRepo.create({name: 'eraser', categoryId: 2}); - // 'id' is the foreign key in Category in respect to Product when we talk about belongsTo - const category = await findByForeignKeys( - categoryRepo, - 'id', - pens.categoryId, - ); - - const result = buildLookupMap( - category, - 'id', - reduceAsSingleItem, - ); - const expected = new Map(); - expected.set(1, cat); - expect(result).to.eql(expected); - }); - - it('returns multiple instances when multiple target instances are passed in', async () => { - const cat1 = await categoryRepo.create({id: 1, name: 'Angus'}); - const cat2 = await categoryRepo.create({id: 2, name: 'Nola'}); - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - const pencils = await productRepo.create({ - name: 'pencils', - categoryId: 1, - }); - const erasers = await productRepo.create({ - name: 'erasers', - categoryId: 2, - }); - // 'id' is the foreign key in Category in respect to Product when we talk about belongsTo - const category = await findByForeignKeys(categoryRepo, 'id', [ - pens.categoryId, - pencils.categoryId, - erasers.categoryId, - ]); - - const result = buildLookupMap( - category, - 'id', - reduceAsSingleItem, - ); - const expected = new Map(); - expected.set(1, cat1); - expected.set(2, cat2); - expect(result).to.eql(expected); - }); - }); -}); diff --git a/packages/repository/src/__tests__/unit/repositories/relations-helpers/find-by-foreign-keys.helpers.unit.ts b/packages/repository/src/__tests__/unit/repositories/relations-helpers/find-by-foreign-keys.helpers.unit.ts deleted file mode 100644 index 71fad2147584..000000000000 --- a/packages/repository/src/__tests__/unit/repositories/relations-helpers/find-by-foreign-keys.helpers.unit.ts +++ /dev/null @@ -1,90 +0,0 @@ -// 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 {findByForeignKeys} from '../../../..'; -import {ProductRepository, testdb} from './relations-helpers-fixtures'; - -describe('findByForeignKeys', () => { - let productRepo: ProductRepository; - - before(() => { - productRepo = new ProductRepository(testdb); - }); - - beforeEach(async () => { - await productRepo.deleteAll(); - }); - - it('returns an empty array when no foreign keys are passed in', async () => { - const fkIds: number[] = []; - await productRepo.create({id: 1, name: 'product', categoryId: 1}); - const products = await findByForeignKeys(productRepo, 'categoryId', fkIds); - expect(products).to.be.empty(); - }); - - 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 an empty array when no instances have the foreign key values', async () => { - await productRepo.create({id: 1, name: 'product', categoryId: 1}); - const products = await findByForeignKeys(productRepo, 'categoryId', [2, 3]); - 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('includes instances when there is one value in the array of 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', [2]); - expect(products).to.deepEqual([pencils]); - expect(products).to.not.containDeep(pens); - }); - - 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 () => { - await expect( - findByForeignKeys(productRepo, 'categoryId', [1], {limit: 1}), - ).to.be.rejectedWith('scope is not supported'); - }); - - it('does not throw an error if scope is passed in and is undefined or empty', async () => { - let products = await findByForeignKeys( - productRepo, - 'categoryId', - [1], - undefined, - {}, - ); - expect(products).to.be.empty(); - products = await findByForeignKeys(productRepo, 'categoryId', 1, {}, {}); - expect(products).to.be.empty(); - }); -}); diff --git a/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.ts b/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.unit.ts similarity index 79% rename from packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.ts rename to packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.unit.ts index 1a19c137ee98..dbab6f315166 100644 --- a/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.ts +++ b/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation-helpers.unit.ts @@ -5,11 +5,11 @@ import {expect} from '@loopback/testlab'; import {flattenTargetsOfOneToManyRelation} from '../../../..'; -import {createProduct, createCategory} from './relations-helpers-fixtures'; +import {createProduct} from './relations-helpers-fixtures'; describe('flattenTargetsOfOneToManyRelation', () => { - describe('get the result of single sourceId for hasMany relation', async () => { - it('get the result of using reduceAsArray strategy', async () => { + describe('gets the result of using reduceAsArray strategy for hasMany relation', async () => { + it('gets the result of passing in a single sourceId', async () => { const pen = createProduct({name: 'pen', categoryId: 1}); const pencil = createProduct({name: 'pencil', categoryId: 1}); createProduct({name: 'eraser', categoryId: 2}); @@ -21,7 +21,7 @@ describe('flattenTargetsOfOneToManyRelation', () => { ); expect(result).to.eql([[pen, pencil]]); }); - it('get the result of multiple sourceIds for hasMany relation', async () => { + it('gets the result of passing in multiple sourceIds', async () => { const pen = createProduct({name: 'pen', categoryId: 1}); const pencil = createProduct({name: 'pencil', categoryId: 1}); const eraser = createProduct({name: 'eraser', categoryId: 2}); diff --git a/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation.helpers.ts b/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation.helpers.ts deleted file mode 100644 index 474d619fed15..000000000000 --- a/packages/repository/src/__tests__/unit/repositories/relations-helpers/flatten-targets-of-one-to-many-relation.helpers.ts +++ /dev/null @@ -1,71 +0,0 @@ -// 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 { - findByForeignKeys, - flattenTargetsOfOneToManyRelation, -} from '../../../..'; -import { - CategoryRepository, - ProductRepository, - testdb, -} from './relations-helpers-fixtures'; - -describe('flattenTargetsOfOneToManyRelation', () => { - let productRepo: ProductRepository; - let categoryRepo: CategoryRepository; - - before(() => { - productRepo = new ProductRepository(testdb); - categoryRepo = new CategoryRepository(testdb, async () => productRepo); - }); - - beforeEach(async () => { - await productRepo.deleteAll(); - await categoryRepo.deleteAll(); - }); - describe('get the result of using reduceAsArray strategy for hasMany relation', async () => { - it('get the result of single sourceId', async () => { - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - const pencils = await productRepo.create({ - name: 'pencils', - categoryId: 1, - }); - await productRepo.create({name: 'eraser', categoryId: 2}); - const targetsFound = await findByForeignKeys( - productRepo, - 'categoryId', - 1, - ); - - const result = flattenTargetsOfOneToManyRelation( - [1], - targetsFound, - 'categoryId', - ); - expect(result).to.eql([[pens, pencils]]); - }); - it('get the result of multiple sourceIds', async () => { - const pens = await productRepo.create({name: 'pens', categoryId: 1}); - const pencils = await productRepo.create({ - name: 'pencils', - categoryId: 1, - }); - const erasers = await productRepo.create({name: 'eraser', categoryId: 2}); - // use [2, 1] here to show the order of sourceIds matters - const targetsFound = await findByForeignKeys(productRepo, 'categoryId', [ - 2, - 1, - ]); - const result = flattenTargetsOfOneToManyRelation( - [2, 1], - targetsFound, - 'categoryId', - ); - expect(result).to.deepEqual([[erasers], [pens, pencils]]); - }); - }); -}); diff --git a/packages/repository/src/__tests__/unit/repositories/relations-helpers/relations-helpers-fixtures.ts b/packages/repository/src/__tests__/unit/repositories/relations-helpers/relations-helpers-fixtures.ts index 9b3fa8769646..d6aa0f49cef2 100644 --- a/packages/repository/src/__tests__/unit/repositories/relations-helpers/relations-helpers-fixtures.ts +++ b/packages/repository/src/__tests__/unit/repositories/relations-helpers/relations-helpers-fixtures.ts @@ -14,77 +14,33 @@ import { juggler, model, property, - hasOne, - HasOneRepositoryFactory, } from '../../../..'; -@model() -export class Manufacturer extends Entity { - @property({id: true}) - id: number; - @property() - name: string; - @belongsTo(() => Product) - productId: number; -} -interface ManufacturerRelations { - products?: Product; -} - -export class ManufacturerRepository extends DefaultCrudRepository< - Manufacturer, - typeof Manufacturer.prototype.id, - ManufacturerRelations -> { - public readonly product: BelongsToAccessor< - Product, - typeof Manufacturer.prototype.id - >; - constructor( - dataSource: juggler.DataSource, - productRepository?: Getter, - ) { - super(Manufacturer, dataSource); - if (productRepository) - this.product = this.createBelongsToAccessorFor( - 'product', - productRepository, - ); - } -} - @model() export class Product extends Entity { @property({id: true}) id: number; @property() name: string; - @hasOne(() => Manufacturer) - manufacturer: Manufacturer; @belongsTo(() => Category) categoryId: number; -} -interface ProductRelations { - manufacturer?: Manufacturer; + + constructor(data: Partial) { + super(data); + } } export class ProductRepository extends DefaultCrudRepository< Product, - typeof Product.prototype.id, - ProductRelations + typeof Product.prototype.id > { public readonly category: BelongsToAccessor< Category, typeof Product.prototype.id >; - public readonly manufacturer: HasOneRepositoryFactory< - Manufacturer, - typeof Product.prototype.id - >; constructor( dataSource: juggler.DataSource, categoryRepository?: Getter, - manyfacturerRepository?: Getter, ) { super(Product, dataSource); if (categoryRepository) @@ -92,11 +48,6 @@ export class ProductRepository extends DefaultCrudRepository< 'category', categoryRepository, ); - if (manyfacturerRepository) - this.manufacturer = this.createHasOneRepositoryFactoryFor( - 'manufacturer', - manyfacturerRepository, - ); } } @@ -108,6 +59,9 @@ export class Category extends Entity { name: string; @hasMany(() => Product, {keyTo: 'categoryId'}) products?: Product[]; + constructor(data: Partial) { + super(data); + } } interface CategoryRelations { products?: Product[]; @@ -140,23 +94,9 @@ export const testdb: juggler.DataSource = new juggler.DataSource({ }); export function createCategory(properties: Partial) { - return new Category({ - name: properties.name, - id: properties.id, - } as Category); + return new Category(properties as Category); } export function createProduct(properties: Partial) { - return new Product({ - id: properties.id, - name: properties.name, - categoryId: properties.categoryId, - } as Product); -} - -export function createManufacturer(properties: Partial) { - return new Manufacturer({ - name: properties.name, - productId: properties.productId, - } as Manufacturer); + return new Product(properties as Product); } diff --git a/packages/repository/src/relations/relation.helpers.ts b/packages/repository/src/relations/relation.helpers.ts index 626d057150aa..6e7c454dd580 100644 --- a/packages/repository/src/relations/relation.helpers.ts +++ b/packages/repository/src/relations/relation.helpers.ts @@ -290,7 +290,11 @@ export function deduplicate(input: T[]): T[] { } /** - * checks of the valus is BsonType (mongodb) + * Checks if the value is BsonType (mongodb) + * It uses a general way to check the type ,so that it can detect + * different versions of bson that might be used in the code base. + * Might need to update in the future. + * * @param value */ export function isBsonType(value: unknown): value is object {