diff --git a/lib/application.js b/lib/application.js index fcc9be2e..5a304755 100644 --- a/lib/application.js +++ b/lib/application.js @@ -534,85 +534,12 @@ function configureModel(ModelCtor, config, app) { config.dataSource = dataSource; app.registry.configureModel(ModelCtor, config); - - setSharedMethodSharedProperties(ModelCtor, app, config); -} - -function setSharedMethodSharedProperties(model, app, modelConfigs) { - var settings = {}; - - // apply config.json settings - var config = app.get('remoting'); - var configHasSharedMethodsSettings = config && - config.sharedMethods && - typeof config.sharedMethods === 'object'; - if (configHasSharedMethodsSettings) - util._extend(settings, config.sharedMethods); - - // apply model-config.json settings - var modelConfig = modelConfigs.options; - var modelConfigHasSharedMethodsSettings = modelConfig && - modelConfig.remoting && - modelConfig.remoting.sharedMethods && - typeof modelConfig.remoting.sharedMethods === 'object'; - if (modelConfigHasSharedMethodsSettings) - util._extend(settings, modelConfig.remoting.sharedMethods); - - // validate setting values - Object.keys(settings).forEach(function(setting) { - var settingValue = settings[setting]; - var settingValueType = typeof settingValue; - if (settingValueType !== 'boolean') - throw new TypeError(g.f('Expected boolean, got %s', settingValueType)); - }); - - // set sharedMethod.shared using the merged settings - var sharedMethods = model.sharedClass.methods({includeDisabled: true}); - - // re-map glob style values to regular expressions - var tests = Object - .keys(settings) - .filter(function(setting) { - return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0; - }) - .map(function(setting) { - // Turn * into an testable regexp string - var glob = escapeRegExp(setting).replace(/\*/g, '(.)*'); - return {regex: new RegExp(glob), setting: settings[setting]}; - }) || []; - sharedMethods.forEach(function(sharedMethod) { - // use the specific setting if it exists - var methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name; - var hasSpecificSetting = settings.hasOwnProperty(methodName); - if (hasSpecificSetting) { - if (settings[methodName] === false) { - sharedMethod.sharedClass.disableMethodByName(methodName); - } else { - sharedMethod.shared = true; - } - } else { - tests.forEach(function(glob) { - if (glob.regex.test(methodName)) { - if (glob.setting === false) { - sharedMethod.sharedClass.disableMethodByName(methodName); - } else { - sharedMethod.shared = true; - } - } - }); - } - }); } function clearHandlerCache(app) { app._handlers = undefined; } -// Sanitize all RegExp reserved characters except * for pattern gobbing -function escapeRegExp(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); -} - /** * Listen for connections and update the configured port. * diff --git a/lib/configure-shared-methods.js b/lib/configure-shared-methods.js new file mode 100644 index 00000000..9b335fb4 --- /dev/null +++ b/lib/configure-shared-methods.js @@ -0,0 +1,75 @@ +'use strict'; + +var util = require('util'); +var extend = require('util')._extend; +var g = require('./globalize'); + +module.exports = function(modelCtor, remotingConfig, modelConfig) { + var settings = {}; + + // apply config.json settings + var configHasSharedMethodsSettings = remotingConfig && + remotingConfig.sharedMethods && + typeof remotingConfig.sharedMethods === 'object'; + if (configHasSharedMethodsSettings) + util._extend(settings, remotingConfig.sharedMethods); + + // apply model-config.json settings + const options = modelConfig.options; + var modelConfigHasSharedMethodsSettings = options && + options.remoting && + options.remoting.sharedMethods && + typeof options.remoting.sharedMethods === 'object'; + if (modelConfigHasSharedMethodsSettings) + util._extend(settings, options.remoting.sharedMethods); + + // validate setting values + Object.keys(settings).forEach(function(setting) { + var settingValue = settings[setting]; + var settingValueType = typeof settingValue; + if (settingValueType !== 'boolean') + throw new TypeError(g.f('Expected boolean, got %s', settingValueType)); + }); + + // set sharedMethod.shared using the merged settings + var sharedMethods = modelCtor.sharedClass.methods({includeDisabled: true}); + + // re-map glob style values to regular expressions + var tests = Object + .keys(settings) + .filter(function(setting) { + return settings.hasOwnProperty(setting) && setting.indexOf('*') >= 0; + }) + .map(function(setting) { + // Turn * into an testable regexp string + var glob = escapeRegExp(setting).replace(/\*/g, '(.)*'); + return {regex: new RegExp(glob), setting: settings[setting]}; + }) || []; + sharedMethods.forEach(function(sharedMethod) { + // use the specific setting if it exists + var methodName = sharedMethod.isStatic ? sharedMethod.name : 'prototype.' + sharedMethod.name; + var hasSpecificSetting = settings.hasOwnProperty(methodName); + if (hasSpecificSetting) { + if (settings[methodName] === false) { + sharedMethod.sharedClass.disableMethodByName(methodName); + } else { + sharedMethod.shared = true; + } + } else { + tests.forEach(function(glob) { + if (glob.regex.test(methodName)) { + if (glob.setting === false) { + sharedMethod.sharedClass.disableMethodByName(methodName); + } else { + sharedMethod.shared = true; + } + } + }); + } + }); +}; + +// Sanitize all RegExp reserved characters except * for pattern gobbing +function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g, '\\$&'); +} diff --git a/lib/loopback.js b/lib/loopback.js index 7e0f431d..7d270c6a 100644 --- a/lib/loopback.js +++ b/lib/loopback.js @@ -18,6 +18,7 @@ var merge = require('util')._extend; var assert = require('assert'); var Registry = require('./registry'); var juggler = require('loopback-datasource-juggler'); +var configureSharedMethods = require('./configure-shared-methods'); /** * LoopBack core module. It provides static properties and @@ -78,6 +79,13 @@ function createApplication(options) { app.loopback = loopback; + app.on('modelRemoted', function() { + app.models().forEach(function(Model) { + if (!Model.config) return; + configureSharedMethods(Model, app.get('remoting'), Model.config); + }); + }); + // Create a new instance of models registry per each app instance app.models = function() { return proto.models.apply(this, arguments); diff --git a/lib/registry.js b/lib/registry.js index ea0195a4..de711838 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -179,6 +179,8 @@ Registry.prototype.configureModel = function(ModelCtor, config) { var settings = ModelCtor.settings; var modelName = ModelCtor.modelName; + ModelCtor.config = config; + // Relations if (typeof config.relations === 'object' && config.relations !== null) { var relations = settings.relations = settings.relations || {}; diff --git a/test/loopback.test.js b/test/loopback.test.js index bc9154b4..851abe84 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -832,9 +832,38 @@ describe('loopback', function() { expect(publicMethods).to.be.empty(); }); - it('hides methods for related models using globs', function() { - var TestModel = app.registry.createModel(uniqueModelName); - var RelatedModel = app.registry.createModel(uniqueModelName); + it('hides methods for related models using globs (model configured first)', function() { + const TestModel = app.registry.createModel('TestModel'); + const RelatedModel = app.registry.createModel('RelatedModel'); + app.dataSource('test', {connector: 'memory'}); + app.model(TestModel, { + dataSource: 'test', + relations: { + related: { + type: 'hasOne', + model: RelatedModel, + }, + }, + options: { + remoting: { + sharedMethods: { + '*__related': false, + }, + }, + }, + }); + app.model(RelatedModel, {dataSource: 'test'}); + + const publicMethods = getSharedMethods(TestModel); + + expect(publicMethods).to.not.include.members([ + 'prototype.__create__related', + ]); + }); + + it('hides methods for related models using globs (related model configured first)', function() { + const TestModel = app.registry.createModel('TestModel'); + const RelatedModel = app.registry.createModel('RelatedModel'); app.dataSource('test', {connector: 'memory'}); app.model(RelatedModel, {dataSource: 'test'}); app.model(TestModel, { @@ -854,7 +883,7 @@ describe('loopback', function() { }, }); - var publicMethods = getSharedMethods(TestModel); + const publicMethods = getSharedMethods(TestModel); expect(publicMethods).to.not.include.members([ 'prototype.__create__related',