compiler: Sort models topologically

Sort models topologically using Base->Model as edges. This way
the base models are defined before the models extending them.
This commit is contained in:
Miroslav Bajtoš 2014-06-16 15:07:21 +02:00
parent ef72efa70b
commit 57e96b0d38
3 changed files with 80 additions and 8 deletions

View File

@ -1,6 +1,7 @@
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var toposort = require('toposort');
var ConfigLoader = require('./config-loader');
var debug = require('debug')('loopback:boot:compiler');
@ -176,15 +177,15 @@ function addAllBaseModels(registry, modelNames) {
while (modelNames.length) {
var name = modelNames.shift();
if (visited[name]) continue;
visited[name] = true;
result.push(name);
var definition = registry[name] && registry[name].definition;
if (!definition) continue;
var base = definition.base || definition.options && definition.options.base;
if (!base || base in visited) continue;
visited[base] = true;
var base = getBaseModelName(definition);
// ignore built-in models like User
if (!registry[base]) continue;
@ -195,9 +196,37 @@ function addAllBaseModels(registry, modelNames) {
return result;
}
function getBaseModelName(modelDefinition) {
if (!modelDefinition)
return undefined;
return modelDefinition.base ||
modelDefinition.options && modelDefinition.options.base;
}
function sortByInheritance(instructions) {
// TODO implement topological sort
return instructions.reverse();
// create edges Base name -> Model name
var edges = instructions
.map(function(inst) {
return [getBaseModelName(inst.definition), inst.name];
});
var sortedNames = toposort(edges);
var instructionsByModelName = {};
instructions.forEach(function(inst) {
instructionsByModelName[inst.name] = inst;
});
return sortedNames
// convert to instructions
.map(function(name) {
return instructionsByModelName[name];
})
// remove built-in models
.filter(function(inst) {
return !!inst;
});
}
function findModelDefinitions(rootDir, sources) {

View File

@ -23,9 +23,10 @@
"url": "https://github.com/strongloop/loopback-boot/blob/master/LICENSE"
},
"dependencies": {
"underscore": "^1.6.0",
"commondir": "0.0.1",
"debug": "^0.8.1",
"commondir": "0.0.1"
"toposort": "^0.2.10",
"underscore": "^1.6.0"
},
"devDependencies": {
"loopback": "^1.5.0",

View File

@ -359,6 +359,48 @@ describe('compiler', function() {
var modelNames = instructions.models.map(getNameProperty);
expect(modelNames).to.eql(['Car']);
});
it('sorts models, base models first', function() {
appdir.createConfigFilesSync({}, {}, {
Vehicle: { dataSource: 'db' },
FlyingCar: { dataSource: 'db' },
Car: { dataSource: 'db' }
});
appdir.writeConfigFileSync('models/car.json', {
name: 'Car',
base: 'Vehicle'
});
appdir.writeConfigFileSync('models/vehicle.json', {
name: 'Vehicle'
});
appdir.writeConfigFileSync('models/flying-car.json', {
name: 'FlyingCar',
base: 'Car'
});
var instructions = boot.compile(appdir.PATH);
var modelNames = instructions.models.map(getNameProperty);
expect(modelNames).to.eql(['Vehicle', 'Car', 'FlyingCar']);
});
it('detects circular Model dependencies', function() {
appdir.createConfigFilesSync({}, {}, {
Vehicle: { dataSource: 'db' },
Car: { dataSource: 'db' }
});
appdir.writeConfigFileSync('models/car.json', {
name: 'Car',
base: 'Vehicle'
});
appdir.writeConfigFileSync('models/vehicle.json', {
name: 'Vehicle',
base: 'Car'
});
expect(function() { boot.compile(appdir.PATH); })
.to.throw(/cyclic dependency/i);
});
});
});