From fce609b2b3ebe5a63d1e1ede3b1ccc49ea051922 Mon Sep 17 00:00:00 2001 From: Hage Yaapa Date: Fri, 4 Oct 2019 18:03:23 +0530 Subject: [PATCH] feat: add defineCrudRepositoryClass Add `defineCrudRepositoryClass` - a helper to create named repository classes --- packages/rest-crud/README.md | 70 +++++++++++++++++-- .../default-model-crud-rest.acceptance.ts | 11 ++- .../unit/define-crud-repository-class.unit.ts | 22 ++++++ packages/rest-crud/src/index.ts | 1 + packages/rest-crud/src/repository-builder.ts | 55 +++++++++++++++ 5 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 packages/rest-crud/src/__tests__/unit/define-crud-repository-class.unit.ts create mode 100644 packages/rest-crud/src/repository-builder.ts diff --git a/packages/rest-crud/README.md b/packages/rest-crud/README.md index de6814af746c..e596fdf0a8c2 100644 --- a/packages/rest-crud/README.md +++ b/packages/rest-crud/README.md @@ -4,8 +4,8 @@ REST API controller implementing default CRUD semantics. ## Overview -This module allows applications to quickly expose a model via REST API without -having to implement a custom controller class. +This module allows applications to quickly expose models via REST API without +having to implement custom controller or repository classes. ## Installation @@ -15,11 +15,19 @@ npm install --save @loopback/rest-crud ## Basic use -1. Define your model class, e.g. using `lb4 model` tool. +`@loopback/rest-crud` exposes two helper methods (`defineCrudRestController` and +`defineCrudRepositoryClass`) for creating controllers and respositories using +code. -2. Create a Repository class, e.g. using `lb4 repository` tool. +For the examples in the following sections, we are assuming a model named +`Product`, and a datasource named `db` have already been created. -3. Create a REST CRUD controller class for your model. +### Creating a CRUD Controller + +Here is how you would use `defineCrudRestController` for exposing the CRUD +endpoints of an existing model with a respository. + +1. Create a REST CRUD controller class for your model. ```ts const ProductController = defineCrudRestController< @@ -29,18 +37,66 @@ npm install --save @loopback/rest-crud >(Product, {basePath: '/products'}); ``` -4. Set up dependency injection for the ProductController. +2. Set up dependency injection for the ProductController. ```ts inject('repositories.ProductRepository')(ProductController, undefined, 0); ``` -5. Register the controller with your application. +3. Register the controller with your application. ```ts app.controller(ProductController); ``` +### Creating a CRUD repository + +Use the `defineCrudRepositoryClass` method to create named repositories (based +on the Model) for your app. + +Usage example: + +```ts +const db = new juggler.DataSource({connector: 'memory'}); +const ProductRepository = defineCrudRepositoryClass(Product); +const repo = new ProductRepository(db); +``` + +### Integrated example + +Here is an example of an app which uses `defineCrudRepositoryClass` and +`defineCrudRestController` to fulfill its repository and controller +requirements. + +```ts +export class TryApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + constructor(options: ApplicationConfig = {}) { + ... + } + + async boot():Promise { + await super.boot(); + + const ProductRepository = defineCrudRepositoryClass(Product); + const repoBinding = this.repository(ProductRepository); + + inject('datasources.db')(ProductRepository, undefined, 0); + this.repository(ProductRepository); + + const ProductController = defineCrudRestController< + Product, + typeof Product.prototype.id, + 'id' + >(Product, {basePath: '/products'}); + + inject(repoBinding.key)(ProductController, undefined, 0); + this.controller(ProductController); + } +} +``` + ## Contributions - [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) diff --git a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts index cfc85aebb9a0..6e6caa438f63 100644 --- a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts +++ b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts @@ -4,7 +4,6 @@ // License text available at https://opensource.org/licenses/MIT import { - DefaultCrudRepository, Entity, EntityCrudRepository, juggler, @@ -19,7 +18,7 @@ import { givenHttpServerConfig, toJSON, } from '@loopback/testlab'; -import {defineCrudRestController} from '../..'; +import {defineCrudRepositoryClass, defineCrudRestController} from '../..'; // In this test scenario, we create a product with a required & an optional // property and use the default model settings (strict mode, forceId). @@ -295,10 +294,10 @@ describe('CrudRestController for a simple Product model', () => { async function setupTestScenario() { const db = new juggler.DataSource({connector: 'memory'}); - repo = new DefaultCrudRepository( - Product, - db, - ); + + const ProductRepository = defineCrudRepositoryClass(Product); + + repo = new ProductRepository(db); const CrudRestController = defineCrudRestController< Product, diff --git a/packages/rest-crud/src/__tests__/unit/define-crud-repository-class.unit.ts b/packages/rest-crud/src/__tests__/unit/define-crud-repository-class.unit.ts new file mode 100644 index 000000000000..c8b471f7e7d6 --- /dev/null +++ b/packages/rest-crud/src/__tests__/unit/define-crud-repository-class.unit.ts @@ -0,0 +1,22 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest-crud +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Entity, model, property} from '@loopback/repository'; +import {expect} from '@loopback/testlab'; +import {defineCrudRepositoryClass} from '../..'; + +describe('defineCrudRepositoryClass', () => { + it('should generate repository based on Model name', async () => { + @model() + class Product extends Entity { + @property({id: true}) + id: number; + } + + const ProductRepository = defineCrudRepositoryClass(Product); + + expect(ProductRepository.name).to.equal('ProductRepository'); + }); +}); diff --git a/packages/rest-crud/src/index.ts b/packages/rest-crud/src/index.ts index a099ac3a456c..4a6f68f068e8 100644 --- a/packages/rest-crud/src/index.ts +++ b/packages/rest-crud/src/index.ts @@ -4,3 +4,4 @@ // License text available at https://opensource.org/licenses/MIT export * from './crud-rest.controller'; +export * from './repository-builder'; diff --git a/packages/rest-crud/src/repository-builder.ts b/packages/rest-crud/src/repository-builder.ts new file mode 100644 index 000000000000..84d59c0e28da --- /dev/null +++ b/packages/rest-crud/src/repository-builder.ts @@ -0,0 +1,55 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest-crud +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + DefaultCrudRepository, + Entity, + EntityCrudRepository, + juggler, +} from '@loopback/repository'; +import * as assert from 'assert'; + +/** + * Create (define) a repository class for the given model. + * + * Example usage: + * + * ```ts + * const ProductRepository = defineCrudRepositoryClass(Product); + * ``` + * + * @param modelCtor A model class, e.g. `Product`. + */ +export function defineCrudRepositoryClass< + T extends Entity, + IdType, + Relations extends object = {} +>( + entityClass: typeof Entity & {prototype: T}, +): RepositoryClass { + const repoName = entityClass.name + 'Repository'; + const defineNamedRepo = new Function( + 'EntityCtor', + 'BaseRepository', + `return class ${repoName} extends BaseRepository { + constructor(dataSource) { + super(EntityCtor, dataSource); + } + };`, + ); + + // TODO(bajtos) make DefaultCrudRepository configurable (?) + const repo = defineNamedRepo(entityClass, DefaultCrudRepository); + assert.equal(repo.name, repoName); + return repo; +} + +export interface RepositoryClass< + T extends Entity, + IdType, + Relations extends object +> { + new (ds: juggler.DataSource): EntityCrudRepository; +}