Pass `app` to fn exported by auto-required script

When a script in `models/` or `boot/` exports a function which is not
a loopback.Model constructor, the bootstrapper immediatelly calls
this exported function wit the current `app` object.

This is providing a dependency injection mechanism for boot scripts,
so that they no longer need to know where to find the `app` object.

Note: the dependency injection is optional. Existing code getting
`app` reference via `require('../app')` will continue to work.
This commit is contained in:
Miroslav Bajtoš 2014-05-27 16:11:54 +02:00
parent 9930934686
commit 3ba43e1197
2 changed files with 52 additions and 6 deletions

View File

@ -2,6 +2,7 @@ var assert = require('assert');
var fs = require('fs');
var path = require('path');
var _ = require('underscore');
var loopback = require('loopback');
var ConfigLoader = require('./lib/config-loader');
/**
@ -195,7 +196,7 @@ exports = module.exports = function bootLoopBackApp(app, options) {
// try to attach models to dataSources by type
try {
require('loopback').autoAttach();
loopback.autoAttach();
} catch(e) {
if(e.name === 'AssertionError') {
console.warn(e);
@ -213,8 +214,8 @@ exports = module.exports = function bootLoopBackApp(app, options) {
}
// require directories
requireDir(path.join(modelsRootDir, 'models'));
requireDir(path.join(appRootDir, 'boot'));
requireDir(path.join(modelsRootDir, 'models'), app);
requireDir(path.join(appRootDir, 'boot'), app);
};
function assertIsValidConfig(name, config) {
@ -232,7 +233,7 @@ function forEachKeyedObject(obj, fn) {
});
}
function requireDir(dir) {
function requireDir(dir, app) {
assert(dir, 'cannot require directory contents without directory name');
var requires = {};
@ -271,9 +272,12 @@ function requireDir(dir) {
return;
}
var basename = path.basename(filename, ext);
var exports = tryRequire(filepath);
if (isFunctionNotModelCtor(exports))
exports(app);
requires[basename] = tryRequire(filepath);
var basename = path.basename(filename, ext);
requires[basename] = exports;
});
return requires;
@ -296,4 +300,9 @@ function tryReadDir() {
}
}
function isFunctionNotModelCtor(fn) {
return typeof fn === 'function' &&
!(fn.prototype instanceof loopback.Model);
}
exports.ConfigLoader = ConfigLoader;

View File

@ -342,6 +342,43 @@ describe('bootLoopBackApp', function() {
expect(app.models).to.have.property('foo');
expect(global.testData).to.have.property('foo', 'loaded');
});
it('calls function exported by models/model.js', function() {
givenAppInSandbox();
writeAppFile('models/model.js',
'module.exports = function(app) { app.fnCalled = true; };');
var app = loopback();
delete app.fnCalled;
boot(app, appDir);
expect(app.fnCalled, 'exported fn was called').to.be.true();
});
it('calls function exported by boot/init.js', function() {
givenAppInSandbox();
writeAppFile('boot/init.js',
'module.exports = function(app) { app.fnCalled = true; };');
var app = loopback();
delete app.fnCalled;
boot(app, appDir);
expect(app.fnCalled, 'exported fn was called').to.be.true();
});
it('does not call Model ctor exported by models/model.json', function() {
givenAppInSandbox();
writeAppFile('models/model.js',
'var loopback = require("loopback");\n' +
'module.exports = loopback.Model.extend("foo");\n' +
'module.exports.prototype._initProperties = function() {\n' +
' global.fnCalled = true;\n' +
'};');
var app = loopback();
delete global.fnCalled;
boot(app, appDir);
expect(global.fnCalled, 'exported fn was called').to.be.undefined();
});
});
});