Skip to content

Commit

Permalink
fixup! apply review feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
biniam committed Oct 22, 2018
1 parent 076cb9c commit 2b6221b
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {property} from '../../decorators/model.decorator';
import {Entity, EntityResolver} from '../../model';
import {relation} from '../relation.decorator';
import {HasOneDefinition, RelationType} from '../relation.types';
Expand Down
52 changes: 24 additions & 28 deletions packages/repository/src/relations/has-one/has-one.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
// License text available at https://opensource.org/licenses/MIT

import {Getter} from '@loopback/context';
import {Count, DataObject, Options} from '../../common-types';
import {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';

Expand All @@ -28,21 +27,17 @@ export interface HasOneRepository<Target extends Entity> {
targetModelData: DataObject<Target>,
options?: Options,
): Promise<Target>;
/**
* Find target model instance
* @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[]>;

/**
* Find the only target model instance that belongs to the declaring model.
* @param filter Query filter
* @param filter Query filter without a Where condition
* @param options Options for the operations
* @returns A promise of the target object or null if not found.
*/
findOne(filter?: Filter<Target>, options?: Options): Promise<Target | null>;
get(
filter?: Exclude<Filter<Target>, Where<Target>>,
options?: Options,
): Promise<Target | undefined>;
}

export class DefaultHasOneRepository<
Expand All @@ -66,32 +61,33 @@ export class DefaultHasOneRepository<
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,
// should we have an in memory LUT instead of a db query to increase
// performance here?
const found = await targetRepository.find(
constrainFilter({}, this.constraint),
);
if (found.length > 0) {
throw new Error(
'HasOne relation does not allow creation of more than one target model instance',
);
} else {
return await targetRepository.create(
constrainDataObject(targetModelData, this.constraint),
options,
);
}
}

async findOne(
filter?: Filter<TargetEntity>,
async get(
filter?: Exclude<Filter<TargetEntity>, Where<TargetEntity>>,
options?: Options,
): Promise<TargetEntity | null> {
): Promise<TargetEntity | undefined> {
const targetRepository = await this.getTargetRepository();
const found = await targetRepository.find(
Object.assign({limit: 1}, constrainFilter(filter, this.constraint)),
options,
);
// TODO: throw EntityNotFound error when target model instance not found
return found[0];
}
}
2 changes: 1 addition & 1 deletion packages/repository/src/relations/relation.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export interface HasOneDefinition extends RelationDefinitionBase {
/**
* The foreign key used by the target model.
*
* E.g. when a Customer has many Order instances, then keyTo is "customerId".
* E.g. when a Customer has one Address instance, then keyTo is "customerId".
* Note that "customerId" is the default FK assumed by the framework, users
* can provide a custom FK name by setting "keyTo".
*/
Expand Down
17 changes: 0 additions & 17 deletions packages/repository/src/repositories/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,6 @@ export interface EntityCrudRepository<T extends Entity, ID>
* Promise<false>
*/
exists(id: ID, options?: Options): Promise<boolean>;

/**
* Find one matching record. Same as 'find', but limited to one result.
* @param filter Query filter
* @param options Options for the operations
* @returns A promise of the target object or null if not found.
*/
findOne(filter?: Filter<T>, options?: Options): Promise<T | null>;
}

/**
Expand Down Expand Up @@ -287,15 +279,6 @@ export class CrudRepositoryImpl<T extends Entity, ID>
return entities[0];
}

findOne(
filter?: Filter<T> | undefined,
options?: AnyObject | undefined,
): Promise<T | null> {
// const found = this.find(Object.assign({limit: 1}, filter), options);
// return this.toModel(found);
throw new Error('Method not implemented');
}

update(entity: DataObject<T>, options?: Options): Promise<void> {
return this.updateById(this.entityClass.getIdOf(entity), entity, options);
}
Expand Down
58 changes: 55 additions & 3 deletions packages/repository/test/acceptance/has-one.relation.acceptance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import {
juggler,
repository,
RepositoryMixin,
Filter,
} from '../..';
import {Address} from '../fixtures/models';
import {CustomerRepository, AddressRepository} from '../fixtures/repositories';
import {Where} from '../..';

describe.only('hasOne relation', () => {
// Given a Customer and Order models - see definitions at the bottom
describe('hasOne relation', () => {
// Given a Customer and Address models - see definitions at the bottom

let app: ApplicationWithRepositories;
let controller: CustomerController;
Expand Down Expand Up @@ -49,6 +51,30 @@ describe.only('hasOne relation', () => {
expect(persisted.toObject()).to.deepEqual(address.toObject());
});

it("doesn't allow to create related model instance twice", async () => {
const address = await controller.createCustomerAddress(existingCustomerId, {
street: '123 test avenue',
});
expect(
controller.createCustomerAddress(existingCustomerId, {
street: '456 test street',
zipcode: '44012',
}),
).to.be.rejectedWith(
/does not allow creation of more than one target model instance/,
);
expect(address.toObject()).to.containDeep({
customerId: existingCustomerId,
street: '123 test avenue',
});

const persisted = await addressRepo.findById(address.zipcode);
expect(persisted.toObject()).to.deepEqual(address.toObject());
expect(addressRepo.findById('44012')).to.be.rejectedWith(
/Entity not found/,
);
});

it('can find instance of the related model', async () => {
const address = await controller.createCustomerAddress(existingCustomerId, {
street: '123 test avenue',
Expand All @@ -71,6 +97,25 @@ describe.only('hasOne relation', () => {
expect(persisted[0]).to.deepEqual(foundAddress);
});

it('does not allow where filter to find related model instance', async () => {
const address = await controller.createCustomerAddress(existingCustomerId, {
street: '123 test avenue',
});

const foundAddress = await controller.findCustomerAddressWithFilter(
existingCustomerId,
{where: {street: '123 test avenue'}},
);
// TODO: make sure this test fails when where condition is supplied
// compiler should have errored out (?)
expect(foundAddress).to.containEql(address);

const persisted = await addressRepo.find({
where: {customerId: existingCustomerId},
});
expect(persisted[0]).to.deepEqual(foundAddress);
});

/*---------------- HELPERS -----------------*/

class CustomerController {
Expand All @@ -89,7 +134,14 @@ describe.only('hasOne relation', () => {
}

async findCustomerAddress(customerId: number) {
return await this.customerRepository.address(customerId).findOne();
return await this.customerRepository.address(customerId).get();
}

async findCustomerAddressWithFilter(
customerId: number,
filter: Filter<Address>,
) {
return await this.customerRepository.address(customerId).get(filter);
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/repository/test/fixtures/models/address.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Entity, model, property, ModelDefinition, belongsTo} from '../../..';
import {Entity, model, property, belongsTo} from '../../..';
import {Customer} from './customer.model';

@model()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,11 @@ describe('model decorator', () => {
RELATIONS_KEY,
Customer.prototype,
) || /* istanbul ignore next */ {};
expect(meta.lastOrder).to.eql({
expect(meta.lastOrder).to.containEql({
type: RelationType.hasOne,
name: 'lastOrder',
target: () => Order,
source: Customer,
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
RelationType,
} from '../../..';

describe.only('createHasOneRepositoryFactory', () => {
describe('createHasOneRepositoryFactory', () => {
let customerRepo: CustomerRepository;

beforeEach(givenStubbedCustomerRepo);
Expand Down

0 comments on commit 2b6221b

Please sign in to comment.