From f130dbb15f37923b00d65867d91db359971a1bfa Mon Sep 17 00:00:00 2001 From: Raphael Dai Date: Sun, 17 Feb 2019 16:54:14 +0000 Subject: [PATCH] feat(repository): add PATCH and DELETE for HasOne relation --- .../acceptance/has-one.relation.acceptance.ts | 103 +++++++++++++++++- .../relations/has-one/has-one.repository.ts | 39 ++++++- .../repository/src/relations/has-one/index.ts | 1 + 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/packages/repository/src/__tests__/acceptance/has-one.relation.acceptance.ts b/packages/repository/src/__tests__/acceptance/has-one.relation.acceptance.ts index be63a2b39ece..ea70b66c8391 100644 --- a/packages/repository/src/__tests__/acceptance/has-one.relation.acceptance.ts +++ b/packages/repository/src/__tests__/acceptance/has-one.relation.acceptance.ts @@ -7,14 +7,14 @@ import {Application} from '@loopback/core'; import {expect, toJSON} from '@loopback/testlab'; import { ApplicationWithRepositories, + EntityNotFoundError, + Filter, juggler, repository, RepositoryMixin, - Filter, - EntityNotFoundError, } from '../..'; import {Address} from '../fixtures/models'; -import {CustomerRepository, AddressRepository} from '../fixtures/repositories'; +import {AddressRepository, CustomerRepository} from '../fixtures/repositories'; describe('hasOne relation', () => { // Given a Customer and Address models - see definitions at the bottom @@ -115,6 +115,91 @@ describe('hasOne relation', () => { ).to.be.rejectedWith(EntityNotFoundError); }); + it('can PATCH hasOne instances', async () => { + const address = await controller.createCustomerAddress(existingCustomerId, { + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Genas', + province: 'Rhone', + }); + + const patchObject = {city: 'Lyon-Genas'}; + const arePatched = await controller.patchCustomerAddress( + existingCustomerId, + patchObject, + ); + + expect(arePatched).to.deepEqual({count: 1}); + const patchedData = await addressRepo.findById(address.zipcode); + expect(toJSON(patchedData)).to.deepEqual({ + customerId: existingCustomerId, + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Lyon-Genas', + province: 'Rhone', + }); + }); + + it('patches the related instance only', async () => { + const bob = await customerRepo.create({name: 'Bob'}); + await customerRepo.address(bob.id).create({city: 'Paris'}); + + const alice = await customerRepo.create({name: 'Alice'}); + await customerRepo.address(alice.id).create({city: 'London'}); + + const result = await controller.patchCustomerAddress(alice.id, { + city: 'New York', + }); + + expect(result).to.deepEqual({count: 1}); + + const found = await customerRepo.address(bob.id).get(); + expect(toJSON(found)).to.containDeep({city: 'Paris'}); + }); + + it('throws an error when PATCH tries to change the foreignKey', async () => { + try { + await expect( + controller.patchCustomerAddress(existingCustomerId, { + customerId: existingCustomerId + 1, + }), + ).to.be.rejectedWith(/Property "customerId" cannot be changed!/); + } catch (err) {} + }); + + it('can DELETE hasOne relation instances', async () => { + await controller.createCustomerAddress(existingCustomerId, { + street: '1 Amedee Bonnet', + zipcode: '69740', + city: 'Genas', + province: 'Rhone', + }); + + const areDeleted = await controller.deleteCustomerAddress( + existingCustomerId, + ); + expect(areDeleted).to.deepEqual({count: 1}); + + await expect( + controller.findCustomerAddress(existingCustomerId), + ).to.be.rejectedWith(EntityNotFoundError); + }); + + it('deletes the related model instance only', async () => { + const bob = await customerRepo.create({name: 'Bob'}); + await customerRepo.address(bob.id).create({city: 'Paris'}); + + const alice = await customerRepo.create({name: 'Alice'}); + await customerRepo.address(alice.id).create({city: 'London'}); + + const result = await controller.deleteCustomerAddress(alice.id); + + expect(result).to.deepEqual({count: 1}); + + const found = await addressRepo.find(); + expect(found).to.have.length(1); + }); + /*---------------- HELPERS -----------------*/ class CustomerController { @@ -142,6 +227,18 @@ describe('hasOne relation', () => { ) { return await this.customerRepository.address(customerId).get(filter); } + async patchCustomerAddress( + customerId: number, + addressData: Partial
, + ) { + return await this.customerRepository + .address(customerId) + .patch(addressData); + } + + async deleteCustomerAddress(customerId: number) { + return await this.customerRepository.address(customerId).delete(); + } } function givenApplicationWithMemoryDB() { diff --git a/packages/repository/src/relations/has-one/has-one.repository.ts b/packages/repository/src/relations/has-one/has-one.repository.ts index 5d268cfeb584..a94d06a41877 100644 --- a/packages/repository/src/relations/has-one/has-one.repository.ts +++ b/packages/repository/src/relations/has-one/has-one.repository.ts @@ -4,15 +4,16 @@ // License text available at https://opensource.org/licenses/MIT import {Getter} from '@loopback/context'; -import {DataObject, Options} from '../../common-types'; +import {Count, DataObject, Options} from '../../common-types'; +import {EntityNotFoundError} from '../../errors'; import {Entity} from '../../model'; import {Filter} from '../../query'; import { constrainDataObject, constrainFilter, + constrainWhere, EntityCrudRepository, } from '../../repositories'; -import {EntityNotFoundError} from '../../errors'; /** * CRUD operations for a target repository of a HasMany relation @@ -39,6 +40,21 @@ export interface HasOneRepository { filter?: Pick, Exclude, 'where'>>, options?: Options, ): Promise; + + /** + * Delete the related target model instance + * @param options + * @returns A promise which resolves the deleted target model instances + */ + delete(options?: Options): Promise; + + /** + * Patch the related target model instance + * @param dataObject The target model fields and their new values to patch + * @param options + * @returns A promise which resolves the patched target model instances + */ + patch(dataObject: DataObject, options?: Options): Promise; } export class DefaultHasOneRepository< @@ -87,4 +103,23 @@ export class DefaultHasOneRepository< } return found[0]; } + async delete(options?: Options): Promise { + const targetRepository = await this.getTargetRepository(); + return targetRepository.deleteAll( + constrainWhere({}, this.constraint), + options, + ); + } + + async patch( + dataObject: DataObject, + options?: Options, + ): Promise { + const targetRepository = await this.getTargetRepository(); + return await targetRepository.updateAll( + constrainDataObject(dataObject, this.constraint), + constrainWhere({}, this.constraint), + options, + ); + } } diff --git a/packages/repository/src/relations/has-one/index.ts b/packages/repository/src/relations/has-one/index.ts index 9e857dd6b290..d7075e42adfc 100644 --- a/packages/repository/src/relations/has-one/index.ts +++ b/packages/repository/src/relations/has-one/index.ts @@ -4,4 +4,5 @@ // License text available at https://opensource.org/licenses/MIT export * from './has-one.decorator'; +export * from './has-one.repository'; export * from './has-one-repository.factory';