Merge pull request #100 from shlomiassaf:simpleModelDef

Extend options arg to support custom model definitions

Close #100
Close #49
This commit is contained in:
Miroslav Bajtoš 2015-05-05 11:29:30 +02:00
commit 2815a42d81
4 changed files with 254 additions and 3 deletions

View File

@ -5,6 +5,7 @@
"try",
"catch"
],
"disallowMultipleVarDecl": "exceptUndefined",
"disallowSpacesInsideObjectBrackets": null,
"requireSpaceAfterLineComment": true,
"maximumLineLength": {

View File

@ -97,6 +97,9 @@ var addInstructionsToBrowserify = require('./lib/bundler');
* @property {String} [appConfigRootDir] Directory to use when loading
* `config.json`. Defaults to `appRootDir`.
* @property {Object} [models] Object containing `Model` configurations.
* @property {Array} [modelDefinitions] List of model definitions to use.
* When `options.modelDefinitions` is provided, loopback-boot does not
* search filesystem and use only the models provided in this argument.
* @property {Object} [dataSources] Object containing `DataSource` definitions.
* @property {String} [modelsRootDir] Directory to use when loading
* `model-config.json`. Defaults to `appRootDir`.

View File

@ -82,7 +82,7 @@ module.exports = function compile(options) {
var modelSources = options.modelSources || modelsMeta.sources || ['./models'];
var modelInstructions = buildAllModelInstructions(
modelsRootDir, modelsConfig, modelSources);
modelsRootDir, modelsConfig, modelSources, options.modelDefinitions);
var mixinDirs = options.mixinDirs || [];
var mixinInstructions = buildAllMixinInstructions(
@ -192,8 +192,10 @@ 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,44 @@ function sortByInheritance(instructions) {
});
}
function verifyModelDefinitions(rootDir, modelDefinitions) {
if (!modelDefinitions || modelDefinitions.length < 1) {
return undefined;
}
var registry = {};
modelDefinitions.forEach(function(definition, idx) {
if (definition.sourceFile) {
var fullPath = path.resolve(rootDir, definition.sourceFile);
definition.sourceFile = fixFileExtension(
fullPath,
tryReadDir(path.dirname(fullPath)),
true);
if (!definition.sourceFile) {
debug('Model source code not found: %s - %s', definition.sourceFile);
}
}
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 = {};

View File

@ -78,6 +78,207 @@ 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' }
};
it('loads model without definition', function() {
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-without-definition': {
dataSource: 'the-db'
}
},
modelDefinitions: [],
dataSources: dataSources
});
expect(instruction.models[0].name)
.to.equal('model-without-definition');
expect(instruction.models[0].definition).to.equal(undefined);
expect(instruction.models[0].sourceFile).to.equal(undefined);
});
it('loads coffeescript models', function() {
var modelScript = appdir.writeFileSync(
'custom-models/coffee-model-with-definition.coffee', '');
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'coffee-model-with-definition': {
dataSource: 'the-db'
}
},
modelDefinitions: [
{
definition: {
name: 'coffee-model-with-definition'
},
sourceFile: modelScript
}
],
dataSources: dataSources
});
expect(instruction.models[0].name)
.to.equal('coffee-model-with-definition');
expect(instruction.models[0].definition).to.eql({
name: 'coffee-model-with-definition'
});
expect(instruction.models[0].sourceFile).to.equal(modelScript);
});
it('handles sourceFile path without extension (.js)', function() {
var modelScript = appdir.writeFileSync(
'custom-models/model-without-ext.coffee',
'');
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-without-ext': {
dataSource: 'the-db'
}
},
modelDefinitions: [{
definition: {
name: 'model-without-ext'
},
sourceFile: pathWithoutExtension(modelScript)
}],
dataSources: dataSources
});
expect(instruction.models[0].name).to.equal('model-without-ext');
expect(instruction.models[0].sourceFile).to.equal(modelScript);
});
it('handles sourceFile path without extension (.coffee)', function() {
var modelScript = appdir.writeFileSync(
'custom-models/model-without-ext.coffee',
'');
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-without-ext': {
dataSource: 'the-db'
}
},
modelDefinitions: [{
definition: {
name: 'model-without-ext'
},
sourceFile: pathWithoutExtension(modelScript)
}],
dataSources: dataSources
});
expect(instruction.models[0].name).to.equal('model-without-ext');
expect(instruction.models[0].sourceFile).to.equal(modelScript);
});
it('sets source file path if the file exist', function() {
var modelScript = appdir.writeFileSync(
'custom-models/model-with-definition.js',
'');
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-with-definition': {
dataSource: 'the-db'
}
},
modelDefinitions: [
{
definition: {
name: 'model-with-definition'
},
sourceFile: modelScript
}
],
dataSources: dataSources
});
expect(instruction.models[0].name).to.equal('model-with-definition');
expect(instruction.models[0].definition).not.to.equal(undefined);
expect(instruction.models[0].sourceFile).to.equal(modelScript);
});
it('does not set source file path if the file does not exist.',
function() {
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-with-definition-with-falsey-source-file': {
dataSource: 'the-db'
}
},
modelDefinitions: [
{
definition: {
name: 'model-with-definition-with-falsey-source-file'
},
sourceFile: appdir.resolve('custom-models',
'file-does-not-exist.js')
}
],
dataSources: dataSources
});
expect(instruction.models[0].name)
.to.equal('model-with-definition-with-falsey-source-file');
expect(instruction.models[0].definition).not.to.equal(undefined);
expect(instruction.models[0].sourceFile).to.equal(undefined);
});
it('does not set source file path if no source file supplied.',
function() {
var instruction = boot.compile({
appRootDir: appdir.PATH,
models: {
'model-with-definition-without-source-file-property': {
dataSource: 'the-db'
}
},
modelDefinitions: [
{
definition: {
name: 'model-with-definition-without-source-file-property'
}
// sourceFile is not set
}
],
dataSources: dataSources
});
expect(instruction.models[0].name)
.to.equal('model-with-definition-without-source-file-property');
expect(instruction.models[0].definition).not.to.equal(undefined);
expect(instruction.models[0].sourceFile).to.equal(undefined);
});
it('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: 'another-model'
}
}
],
dataSources: dataSources
});
expect(instruction.models.map(getNameProperty))
.to.eql(['some-model']);
});
});
});
describe('from directory', function() {
@ -1737,3 +1938,9 @@ function givenMiddlewareEntrySync(config) {
function expectFirstMiddlewareParams(instructions) {
return expect(instructions.middleware.middleware[0].config.params);
}
function pathWithoutExtension(value) {
return path.join(
path.dirname(value),
path.basename(value, path.extname(value)));
}