From 3ba43e119732996659d0ede65ff1b76ee7a2223d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 27 May 2014 16:11:54 +0200 Subject: [PATCH] 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. --- index.js | 21 +++++++++++++++------ test/boot.test.js | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 77e5efd..240c574 100644 --- a/index.js +++ b/index.js @@ -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; diff --git a/test/boot.test.js b/test/boot.test.js index 745dcef..947d45b 100644 --- a/test/boot.test.js +++ b/test/boot.test.js @@ -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(); + }); }); });