Extend options arg to support custom model definitions
Extend the options argument of boot/compile to allow the developer to supply custom model definitions (to replace the result of findModelDefinitions). Together with the existing options.models, it allows to specify all model-related data and still use the benefits which loopback-boot provides (most notably loading models in order defined by inheritance, i.e. base models are loaded before models that are extending them).
This commit is contained in:
parent
0e19f0a1b2
commit
b6c60a297d
|
@ -81,8 +81,10 @@ module.exports = function compile(options) {
|
|||
delete modelsConfig._meta;
|
||||
|
||||
var modelSources = options.modelSources || modelsMeta.sources || ['./models'];
|
||||
var modelDefinitions = options.modelDefinitions || undefined;
|
||||
|
||||
var modelInstructions = buildAllModelInstructions(
|
||||
modelsRootDir, modelsConfig, modelSources);
|
||||
modelsRootDir, modelsConfig, modelSources, modelDefinitions);
|
||||
|
||||
var mixinDirs = options.mixinDirs || [];
|
||||
var mixinInstructions = buildAllMixinInstructions(
|
||||
|
@ -192,8 +194,8 @@ function tryReadDir() {
|
|||
}
|
||||
}
|
||||
|
||||
function buildAllModelInstructions(rootDir, modelsConfig, sources) {
|
||||
var registry = findModelDefinitions(rootDir, sources);
|
||||
function buildAllModelInstructions(rootDir, modelsConfig, sources, modelDefinitions) {
|
||||
var registry = verifyModelDefinitions(rootDir, modelDefinitions) || findModelDefinitions(rootDir, sources);
|
||||
|
||||
var modelNamesToBuild = addAllBaseModels(registry, Object.keys(modelsConfig));
|
||||
|
||||
|
@ -274,6 +276,68 @@ function sortByInheritance(instructions) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns matching files with a supported `sourceFile` extension like `.js` or `.coffee`
|
||||
* @param allFiles {Array}
|
||||
* @param basename {string}
|
||||
* @returns {Array}
|
||||
*/
|
||||
function filterValidSourceFiles(allFiles, basename) {
|
||||
var base, ext, validFileType;
|
||||
return allFiles
|
||||
.filter(function(f) {
|
||||
ext = path.extname(f);
|
||||
base = path.basename(f, ext);
|
||||
validFileType = (ext !== '.node') && (ext !== '.json') &&
|
||||
((typeof require.extensions[ext]) === 'function');
|
||||
return validFileType && (base === basename);
|
||||
});
|
||||
}
|
||||
|
||||
function verifyModelDefinitions(rootDir, modelDefinitions) {
|
||||
if (!modelDefinitions || modelDefinitions.length < 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var registry = {};
|
||||
modelDefinitions.forEach(function(definition, idx) {
|
||||
if (definition.sourceFile) {
|
||||
try {
|
||||
var fullPath = path.resolve(rootDir, definition.sourceFile);
|
||||
var basename = path.basename(fullPath, path.extname(fullPath));
|
||||
var files = tryReadDir(path.dirname(fullPath));
|
||||
var sourceFile = filterValidSourceFiles(files, basename)[0];
|
||||
|
||||
definition.sourceFile = path.join(path.dirname(fullPath), sourceFile);
|
||||
definition.sourceFile = require.resolve(definition.sourceFile);
|
||||
} catch (err) {
|
||||
debug('Model source code not found: %s - %s',
|
||||
definition.sourceFile,
|
||||
err.code || err);
|
||||
definition.sourceFile = undefined;
|
||||
}
|
||||
} else {
|
||||
definition.sourceFile = undefined;
|
||||
}
|
||||
|
||||
debug('Found model "%s" - %s %s',
|
||||
definition.definition.name,
|
||||
'from options',
|
||||
definition.sourceFile ?
|
||||
path.relative(rootDir, definition.sourceFile) :
|
||||
'(no source file)');
|
||||
|
||||
var modelName = definition.definition.name;
|
||||
if (!modelName) {
|
||||
debug('Skipping model definition without Model name (from options.modelDefinitions @ index %s)', idx);
|
||||
return;
|
||||
}
|
||||
registry[modelName] = definition;
|
||||
});
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
function findModelDefinitions(rootDir, sources) {
|
||||
var registry = {};
|
||||
|
||||
|
|
|
@ -78,6 +78,193 @@ describe('compiler', function() {
|
|||
it('has datasources definition', function() {
|
||||
expect(instructions.dataSources).to.eql(options.dataSources);
|
||||
});
|
||||
|
||||
describe('with custom model definitions', function() {
|
||||
var dataSources = {
|
||||
'the-db': {
|
||||
connector: 'memory',
|
||||
defaultForType: 'db'
|
||||
}
|
||||
};
|
||||
|
||||
it('should loads model without definition.', function() {
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'model-without-definitions': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('model-without-definitions');
|
||||
expect(instruction.models[0].definition).to.equal(undefined);
|
||||
expect(instruction.models[0].sourceFile).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should load coffeescript models', function() {
|
||||
require('coffee-script/register');
|
||||
|
||||
appdir.writeFileSync('custom-models/coffee-model-with-definitions.coffee', '');
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'coffee-model-with-definitions': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: {
|
||||
name: 'coffee-model-with-definitions'
|
||||
},
|
||||
sourceFile: path.join(appdir.PATH, 'custom-models', 'coffee-model-with-definitions.coffee')
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('coffee-model-with-definitions');
|
||||
expect(instruction.models[0].definition).not.to.equal(undefined);
|
||||
expect(instruction.models[0].sourceFile).not.to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should handle sourceFile path without extension.', function() {
|
||||
require('coffee-script/register');
|
||||
|
||||
appdir.writeFileSync('custom-models/coffee-model-without-ext.coffee', '');
|
||||
appdir.writeFileSync('custom-models/js-model-without-ext.js', '');
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'coffee-model-without-ext': {
|
||||
dataSource: 'the-db'
|
||||
},
|
||||
'js-model-without-ext': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: {
|
||||
name: 'coffee-model-without-ext'
|
||||
},
|
||||
sourceFile: path.join(appdir.PATH, 'custom-models', 'coffee-model-without-ext')
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
name: 'js-model-without-ext'
|
||||
},
|
||||
sourceFile: path.join(appdir.PATH, 'custom-models', 'js-model-without-ext')
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('coffee-model-without-ext');
|
||||
expect(instruction.models[0].sourceFile)
|
||||
.to.equal(path.join(appdir.PATH, 'custom-models', 'coffee-model-without-ext.coffee'));
|
||||
|
||||
expect(instruction.models[1].name).to.equal('js-model-without-ext');
|
||||
expect(instruction.models[1].sourceFile)
|
||||
.to.equal(path.join(appdir.PATH, 'custom-models', 'js-model-without-ext.js'));
|
||||
});
|
||||
|
||||
it('should set source file path if the file exist.', function() {
|
||||
appdir.writeFileSync('custom-models/model-with-definitions.js', '');
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'model-with-definitions': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: {
|
||||
name: 'model-with-definitions'
|
||||
},
|
||||
sourceFile: path.join(appdir.PATH, 'custom-models', 'model-with-definitions.js')
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('model-with-definitions');
|
||||
expect(instruction.models[0].definition).not.to.equal(undefined);
|
||||
expect(instruction.models[0].sourceFile)
|
||||
.to.equal(path.join(appdir.PATH, 'custom-models', 'model-with-definitions.js'));
|
||||
});
|
||||
|
||||
it('should not set source file path if the file does not exist.', function() {
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'model-with-definitions-with-falsey-source-file': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: { // sourceFile is a not existing file.
|
||||
name: 'model-with-definitions-with-falsey-source-file'
|
||||
},
|
||||
sourceFile: path.join(appdir.PATH, 'custom-models', 'model-with-definitions-with-falsey-source-file.js')
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('model-with-definitions-with-falsey-source-file');
|
||||
expect(instruction.models[0].definition).not.to.equal(undefined);
|
||||
expect(instruction.models[0].sourceFile).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should not set source file path if no source file supplied.', function() {
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'model-with-definitions-without-source-file-property': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: { // sourceFile is a not existing file.
|
||||
name: 'model-with-definitions-without-source-file-property'
|
||||
}
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
expect(instruction.models[0].name).to.equal('model-with-definitions-without-source-file-property');
|
||||
expect(instruction.models[0].definition).not.to.equal(undefined);
|
||||
expect(instruction.models[0].sourceFile).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('should loads models defined in `models` only.', function() {
|
||||
var instruction = boot.compile({
|
||||
appRootDir: appdir.PATH,
|
||||
models: {
|
||||
'some-model': {
|
||||
dataSource: 'the-db'
|
||||
}
|
||||
},
|
||||
modelDefinitions: [
|
||||
{
|
||||
definition: {
|
||||
name: 'some-model'
|
||||
}
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
name: 'some-fictional-model'
|
||||
}
|
||||
}
|
||||
],
|
||||
dataSources: dataSources
|
||||
});
|
||||
|
||||
expect(instruction.models).to.have.length(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('from directory', function() {
|
||||
|
|
Loading…
Reference in New Issue