Fix relation race condition in model glob
Globs working depended on the order that models were imported. Remote sharing is now re-calculated whenever a new model is remoted.
This commit is contained in:
parent
30f3161c65
commit
d405432b2d
|
@ -534,85 +534,12 @@ function configureModel(ModelCtor, config, app) {
|
||||||
config.dataSource = dataSource;
|
config.dataSource = dataSource;
|
||||||
|
|
||||||
app.registry.configureModel(ModelCtor, config);
|
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) {
|
function clearHandlerCache(app) {
|
||||||
app._handlers = undefined;
|
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.
|
* Listen for connections and update the configured port.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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, '\\$&');
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ var merge = require('util')._extend;
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var Registry = require('./registry');
|
var Registry = require('./registry');
|
||||||
var juggler = require('loopback-datasource-juggler');
|
var juggler = require('loopback-datasource-juggler');
|
||||||
|
var configureSharedMethods = require('./configure-shared-methods');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LoopBack core module. It provides static properties and
|
* LoopBack core module. It provides static properties and
|
||||||
|
@ -78,6 +79,13 @@ function createApplication(options) {
|
||||||
|
|
||||||
app.loopback = loopback;
|
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
|
// Create a new instance of models registry per each app instance
|
||||||
app.models = function() {
|
app.models = function() {
|
||||||
return proto.models.apply(this, arguments);
|
return proto.models.apply(this, arguments);
|
||||||
|
|
|
@ -179,6 +179,8 @@ Registry.prototype.configureModel = function(ModelCtor, config) {
|
||||||
var settings = ModelCtor.settings;
|
var settings = ModelCtor.settings;
|
||||||
var modelName = ModelCtor.modelName;
|
var modelName = ModelCtor.modelName;
|
||||||
|
|
||||||
|
ModelCtor.config = config;
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
if (typeof config.relations === 'object' && config.relations !== null) {
|
if (typeof config.relations === 'object' && config.relations !== null) {
|
||||||
var relations = settings.relations = settings.relations || {};
|
var relations = settings.relations = settings.relations || {};
|
||||||
|
|
|
@ -832,9 +832,38 @@ describe('loopback', function() {
|
||||||
expect(publicMethods).to.be.empty();
|
expect(publicMethods).to.be.empty();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides methods for related models using globs', function() {
|
it('hides methods for related models using globs (model configured first)', function() {
|
||||||
var TestModel = app.registry.createModel(uniqueModelName);
|
const TestModel = app.registry.createModel('TestModel');
|
||||||
var RelatedModel = app.registry.createModel(uniqueModelName);
|
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.dataSource('test', {connector: 'memory'});
|
||||||
app.model(RelatedModel, {dataSource: 'test'});
|
app.model(RelatedModel, {dataSource: 'test'});
|
||||||
app.model(TestModel, {
|
app.model(TestModel, {
|
||||||
|
@ -854,7 +883,7 @@ describe('loopback', function() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var publicMethods = getSharedMethods(TestModel);
|
const publicMethods = getSharedMethods(TestModel);
|
||||||
|
|
||||||
expect(publicMethods).to.not.include.members([
|
expect(publicMethods).to.not.include.members([
|
||||||
'prototype.__create__related',
|
'prototype.__create__related',
|
||||||
|
|
Loading…
Reference in New Issue