Skip to content

Commit 433e46f

Browse files
Keith Aleq Jacksonpersimmons
Keith Aleq Jackson
authored andcommitted
Allow namespacing and shared module exclusion
When connecting to several LoopBack APIs from an Angular app, multiple definitions of LoopBackAuth and LoopBackResource will 'step on' each other, resulting in unpredictable behavior. In addition, if two APIs have models with the same names, these definitions will override each other, depending upon whichever API loaded last. This patch keeps the default behavior consistent, but provides an option to omit the module block that generates the LoopBackAuth, LoopBackAuthRequestInterceptor, and LoopBackResource components. It also provides an option to namespace models with the module name.
1 parent 811ea79 commit 433e46f

File tree

5 files changed

+190
-19
lines changed

5 files changed

+190
-19
lines changed

lib/services.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ module.exports = function generateServices(app, options) {
5454
options = extend({
5555
ngModuleName: 'lbServices',
5656
apiUrl: '/',
57+
includeCommonModules: true,
58+
namespaceModels: false,
59+
namespaceDelimiter: '.',
5760
}, options);
5861

5962
var models = describeModels(app, options);
@@ -67,13 +70,26 @@ module.exports = function generateServices(app, options) {
6770
moduleName: options.ngModuleName,
6871
models: models,
6972
urlBase: options.apiUrl.replace(/\/+$/, ''),
73+
includeCommonModules: options.includeCommonModules,
7074
});
7175
};
7276

77+
function getFormattedModelName(modelName, options) {
78+
// Always capitalize first letter of model name
79+
var resourceModelName = modelName[0].toUpperCase() + modelName.slice(1);
80+
81+
// Prefix with the module name and delimiter if namespacing is on
82+
if (options.namespaceModels) {
83+
resourceModelName = options.ngModuleName +
84+
options.namespaceDelimiter + resourceModelName;
85+
}
86+
return resourceModelName;
87+
}
88+
7389
function describeModels(app, options) {
7490
var result = {};
7591
app.handler('rest').adapter.getClasses().forEach(function(c) {
76-
var name = c.name;
92+
var name = getFormattedModelName(c.name, options);
7793
c.description = c.sharedClass.ctor.settings.description;
7894

7995
if (!c.ctor) {

lib/services.template.ejs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@ if (typeof module !== 'undefined' && typeof exports !== 'undefined' &&
3737

3838
<% for (var modelName in models) {
3939
var meta = models[modelName];
40-
// capitalize the model name
41-
modelName = modelName[0].toUpperCase() + modelName.slice(1);
4240
-%>
4341
/**
4442
* @ngdoc object
@@ -300,7 +298,7 @@ if (typeof module !== 'undefined' && typeof exports !== 'undefined' &&
300298
}]);
301299
302300
<% } // for modelName in models -%>
303-
301+
<% if (includeCommonModules) { %>
304302
module
305303
.factory('LoopBackAuth', function() {
306304
var props = ['accessTokenId', 'currentUserId', 'rememberMe'];
@@ -490,7 +488,8 @@ if (typeof module !== 'undefined' && typeof exports !== 'undefined' &&
490488
return LoopBackResource;
491489
}];
492490
});
493-
<%
491+
<% } // end if (includeCommonModules)
492+
494493
function getJsDocType(arg) {
495494
var type = arg.type == 'any' ? '*' : arg.type;
496495
if (!arg.required) type += '=';

test.e2e/given.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ define(['angular', 'angularMocks', 'angularResource'], function(angular) {
5858
* ```
5959
*/
6060
given.servicesForLoopBackApp = function(options, cb) {
61-
options.name = generateUniqueServiceName(options.name);
61+
if (!options.name) {
62+
options.name = generateUniqueServiceName(options.name);
63+
}
6264

6365
var promise = callSetup(options)
6466
.then(function(config) { return config.servicesUrl; })

test.e2e/spec/services.spec.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,150 @@ define(['angular', 'given', 'util'], function(angular, given, util) {
10441044
});
10451045
});
10461046

1047+
describe('$resource generated with includeCommonModules:false', function() {
1048+
var $injector;
1049+
before(function() {
1050+
return given.servicesForLoopBackApp(
1051+
{
1052+
models: {
1053+
Product: {
1054+
properties: {
1055+
name: 'string',
1056+
price: { type: 'number' },
1057+
},
1058+
},
1059+
},
1060+
includeCommonModules: false,
1061+
})
1062+
.then(function(createInjector) {
1063+
$injector = createInjector();
1064+
});
1065+
});
1066+
1067+
it('does not have "LoopBackAuth module"', function() {
1068+
expect(function() {
1069+
$injector.get('LoopBackAuth');
1070+
}).to.throw(/Unknown provider/);
1071+
});
1072+
1073+
it('does not have "LoopBackResource provider"', function() {
1074+
expect(function() {
1075+
$injector.get('LoopBackResource');
1076+
}).to.throw(/Unknown provider/);
1077+
});
1078+
1079+
it('does not have "LoopBackAuthRequestInterceptor module"',
1080+
function() {
1081+
expect(function() {
1082+
$injector.get('LoopBackAuthRequestInterceptor');
1083+
}).to.throw(/Unknown provider/);
1084+
});
1085+
});
1086+
1087+
describe('$resource generated with includeCommonModules:true (by default)',
1088+
function() {
1089+
var $injector;
1090+
before(function() {
1091+
return given.servicesForLoopBackApp(
1092+
{
1093+
models: {
1094+
Product: {
1095+
properties: {
1096+
name: 'string',
1097+
price: { type: 'number' },
1098+
},
1099+
},
1100+
},
1101+
})
1102+
.then(function(createInjector) {
1103+
$injector = createInjector();
1104+
});
1105+
});
1106+
1107+
it('has "LoopBackAuth module"', function() {
1108+
expect(function() {
1109+
$injector.get('LoopBackAuth');
1110+
}).to.not.throw();
1111+
});
1112+
1113+
it('has "LoopBackResource provider"', function() {
1114+
expect(function() {
1115+
$injector.get('LoopBackResource');
1116+
}).to.not.throw();
1117+
});
1118+
1119+
it('has "LoopBackAuthRequestInterceptor module"',
1120+
function() {
1121+
expect(function() {
1122+
$injector.get('LoopBackAuthRequestInterceptor');
1123+
}).to.not.throw();
1124+
});
1125+
});
1126+
1127+
describe('$resource generated with namespaceModels:true', function() {
1128+
var $injector;
1129+
before(function() {
1130+
return given.servicesForLoopBackApp(
1131+
{
1132+
models: {
1133+
Product: {
1134+
properties: {
1135+
name: 'string',
1136+
price: { type: 'number' },
1137+
},
1138+
},
1139+
},
1140+
name: 'lbServices',
1141+
namespaceModels: true,
1142+
})
1143+
.then(function(createInjector) {
1144+
$injector = createInjector();
1145+
});
1146+
});
1147+
1148+
it('defines the "Product" model as "lbServices.Product"', function() {
1149+
expect(function() {
1150+
$injector.get('Product');
1151+
}).to.throw(/Unknown provider/);
1152+
expect(function() {
1153+
$injector.get('lbServices.Product');
1154+
}).to.not.throw();
1155+
});
1156+
});
1157+
1158+
describe('$resource generated with namespaceModels:true and ' +
1159+
'namespaceDelimiter:_', function() {
1160+
var $injector;
1161+
before(function() {
1162+
return given.servicesForLoopBackApp(
1163+
{
1164+
models: {
1165+
Product: {
1166+
properties: {
1167+
name: 'string',
1168+
price: { type: 'number' },
1169+
},
1170+
},
1171+
},
1172+
name: 'lbServices',
1173+
namespaceModels: true,
1174+
namespaceDelimiter: '_',
1175+
})
1176+
.then(function(createInjector) {
1177+
$injector = createInjector();
1178+
});
1179+
});
1180+
1181+
it('defines the "Product" model as "lbServices.Product"', function() {
1182+
expect(function() {
1183+
$injector.get('Product');
1184+
}).to.throw(/Unknown provider/);
1185+
expect(function() {
1186+
$injector.get('lbServices_Product');
1187+
}).to.not.throw();
1188+
});
1189+
});
1190+
10471191
describe('for models with belongsTo relation', function() {
10481192
var $injector, Town, Country, testData;
10491193
before(function() {

test.e2e/test-server.js

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ masterApp.post('/setup', function(req, res, next) {
6767
var name = opts.name;
6868
var models = opts.models;
6969
var enableAuth = opts.enableAuth;
70-
var includeSchema = opts.includeSchema;
7170
var setupFn = compileSetupFn(name, opts.setupFn);
7271

7372
if (!name)
@@ -105,19 +104,8 @@ masterApp.post('/setup', function(req, res, next) {
105104
res.send(500, err);
106105
return;
107106
}
108-
109107
try {
110-
if (includeSchema) {
111-
// the new options-based API
112-
servicesScript = generator.services(lbApp, {
113-
ngModuleName: name,
114-
apiUrl: apiUrl,
115-
includeSchema: includeSchema,
116-
});
117-
} else {
118-
// the old API, test it to verify backwards compatibility
119-
servicesScript = generator.services(lbApp, name, apiUrl);
120-
}
108+
servicesScript = generateService(generator, lbApp, apiUrl, opts);
121109
} catch (err) {
122110
console.error('Cannot generate services script:', err.stack);
123111
servicesScript = 'throw new Error("Error generating services script.");';
@@ -161,6 +149,28 @@ masterApp.listen(port, function() {
161149
runAndExit(process.argv[2], process.argv.slice(3));
162150
});
163151

152+
function generateService(generator, lbApp, apiUrl, opts) {
153+
var servicesScript;
154+
if (opts.includeSchema !== undefined ||
155+
opts.includeCommonModules !== undefined ||
156+
opts.namespaceModels !== undefined ||
157+
opts.namespaceDelimiter !== undefined) {
158+
// the new options-based API
159+
160+
// build options object for new options-based API
161+
var generatorOptions = opts;
162+
generatorOptions.ngModuleName = opts.name;
163+
generatorOptions.apiUrl = apiUrl;
164+
165+
servicesScript = generator.services(lbApp, generatorOptions);
166+
} else {
167+
// the old API, test it to verify backwards compatibility
168+
servicesScript = generator.services(lbApp, opts.name, apiUrl);
169+
}
170+
171+
return servicesScript;
172+
}
173+
164174
function runAndExit(cmd, args) {
165175
console.log('Running %s %s', cmd, args.join(' '));
166176
var child = require('child_process').spawn(cmd, args, { stdio: 'inherit' });

0 commit comments

Comments
 (0)