Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/boot/src/__tests__/fixtures/multiple-models.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Entity, Model} from '@loopback/repository';

export class Model1 extends Model {}

export class Model2 extends Entity {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright IBM Corp. 2019,2020. All Rights Reserved.
// Node module: @loopback/boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {expect, TestSandbox} from '@loopback/testlab';
import {resolve} from 'path';
import {BooterApp} from '../fixtures/application';

describe('repository booter integration tests', () => {
const sandbox = new TestSandbox(resolve(__dirname, '../../.sandbox'));

const MODELS_TAG = 'model';

let app: BooterApp;

beforeEach('reset sandbox', () => sandbox.reset());
beforeEach(getApp);

it('boots repositories when app.boot() is called', async () => {
const expectedBindings = [
'models.Model1',
'models.Model2',
'models.NoEntity',
'models.Product',
];

await app.boot();

const bindings = app.findByTag(MODELS_TAG).map(b => b.key);
expect(bindings.sort()).to.eql(expectedBindings.sort());
});

async function getApp() {
await sandbox.copyFile(resolve(__dirname, '../fixtures/application.js'));
await sandbox.copyFile(
resolve(__dirname, '../fixtures/no-entity.model.js'),
'models/no-entity.model.js',
);
await sandbox.copyFile(
resolve(__dirname, '../fixtures/product.model.js'),
'models/product.model.js',
);

await sandbox.copyFile(
resolve(__dirname, '../fixtures/multiple-models.model.js'),
'models/multiple-models.model.js',
);

const MyApp = require(resolve(sandbox.path, 'application.js')).BooterApp;
app = new MyApp();
}
});
2 changes: 2 additions & 0 deletions packages/boot/src/boot.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
InterceptorProviderBooter,
LifeCycleObserverBooter,
ModelApiBooter,
ModelBooter,
RepositoryBooter,
ServiceBooter,
} from './booters';
Expand All @@ -35,6 +36,7 @@ export class BootComponent implements Component {
LifeCycleObserverBooter,
InterceptorProviderBooter,
ModelApiBooter,
ModelBooter,
];

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/boot/src/booters/datasource.booter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {BaseArtifactBooter} from './base-artifact.booter';

/**
* A class that extends BaseArtifactBooter to boot the 'DataSource' artifact type.
* Discovered DataSources are bound using `app.controller()`.
* Discovered DataSources are bound using `app.dataSource()`.
*
* Supported phases: configure, discover, load
*
Expand Down
1 change: 1 addition & 0 deletions packages/boot/src/booters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from './datasource.booter';
export * from './interceptor.booter';
export * from './lifecyle-observer.booter';
export * from './model-api.booter';
export * from './model.booter';
export * from './repository.booter';
export * from './service.booter';
73 changes: 73 additions & 0 deletions packages/boot/src/booters/model.booter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright IBM Corp. 2020. All Rights Reserved.
// Node module: @loopback/boot
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {config, Constructor, inject} from '@loopback/context';
import {Application, CoreBindings} from '@loopback/core';
import {ModelMetadataHelper} from '@loopback/repository';
import debugFactory from 'debug';
import {BootBindings} from '../keys';
import {ArtifactOptions, booter} from '../types';
import {BaseArtifactBooter} from './base-artifact.booter';

const debug = debugFactory('loopback:boot:model-booter');

/**
* A class that extends BaseArtifactBooter to boot the 'Model' artifact type.
*
* Supported phases: configure, discover, load
*
* @param app - Application instance
* @param projectRoot - Root of User Project relative to which all paths are resolved
* @param bootConfig - Model Artifact Options Object
*/
@booter('models')
export class ModelBooter extends BaseArtifactBooter {
constructor(
@inject(CoreBindings.APPLICATION_INSTANCE)
public app: Application,
@inject(BootBindings.PROJECT_ROOT) projectRoot: string,
@config()
public modelConfig: ArtifactOptions = {},
) {
super(
projectRoot,
// Set Model Booter Options if passed in via bootConfig
Object.assign({}, ModelDefaults, modelConfig),
);
}

/**
* Uses super method to get a list of Artifact classes. Boot each file by
* creating a DataSourceConstructor and binding it to the application class.
*/
async load() {
await super.load();

for (const cls of this.classes) {
if (!isModelClass(cls)) {
debug('Skipping class %s - no @model is found', cls.name);
continue;
}

debug('Bind class: %s', cls.name);
// We are binding the model class itself
const binding = this.app.bind(`models.${cls.name}`).to(cls).tag('model');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raymondfeng can you please extract the code building the binding to a standalone function that can be used to bind models manually, in cases where @loopback/boot is not involved? For example in extensions.

const binding = createModelClassBinding(cls);
app.add(binding);

Similar helpers we already have: createComponentApplicationBooterBinding, createBindingFromClass, createBodyParserBinding, createServiceBinding.

debug('Binding created for model class %s: %j', cls.name, binding);
}
}
}

/**
* Default ArtifactOptions for DataSourceBooter.
*/
export const ModelDefaults: ArtifactOptions = {
dirs: ['models'],
extensions: ['.model.js'],
nested: true,
};

function isModelClass(cls: Constructor<unknown>) {
return ModelMetadataHelper.getModelMetadata(cls) != null;
}
2 changes: 1 addition & 1 deletion packages/boot/src/booters/service.booter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const debug = debugFactory('loopback:boot:service-booter');

/**
* A class that extends BaseArtifactBooter to boot the 'Service' artifact type.
* Discovered DataSources are bound using `app.controller()`.
* Discovered services are bound using `app.service()`.
*
* Supported phases: configure, discover, load
*
Expand Down