Skip to content

Commit fa0f402

Browse files
committed
Merge pull request #151 from strongloop/feature/improve-remoting-metadata
Improve remoting metadata: register exported models using singular names
2 parents 7230890 + 4699219 commit fa0f402

8 files changed

Lines changed: 150 additions & 13 deletions

File tree

lib/application.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
var DataSource = require('loopback-datasource-juggler').DataSource
66
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
7+
, compat = require('./compat')
78
, assert = require('assert')
89
, fs = require('fs')
910
, RemoteObjects = require('strong-remoting')
@@ -97,8 +98,9 @@ app.disuse = function (route) {
9798
app.model = function (Model, config) {
9899
if(arguments.length === 1) {
99100
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
100-
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property');
101-
this.remotes().exports[Model.pluralModelName] = Model;
101+
assert(Model.modelName, 'Model must have a "modelName" property');
102+
var remotingClassName = compat.getClassNameForRemoting(Model);
103+
this.remotes().exports[remotingClassName] = Model;
102104
this.models().push(Model);
103105
Model.shared = true;
104106
Model.app = this;
@@ -203,7 +205,7 @@ app.remoteObjects = function () {
203205
models.forEach(function (ModelCtor) {
204206
// only add shared models
205207
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
206-
result[ModelCtor.pluralModelName] = ModelCtor;
208+
result[compat.getClassNameForRemoting(ModelCtor)] = ModelCtor;
207209
}
208210
});
209211

lib/compat.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
var assert = require('assert');
2+
3+
/**
4+
* Compatibility layer allowing applications based on an older LoopBack version
5+
* to work with newer versions with minimum changes involved.
6+
*
7+
* You should not use it unless migrating from an older version of LoopBack.
8+
*/
9+
10+
var compat = exports;
11+
12+
/**
13+
* LoopBack versions pre-1.6 use plural model names when registering shared
14+
* classes with strong-remoting. As the result, strong-remoting use method names
15+
* like `Users.create` for the javascript methods like `User.create`.
16+
* This has been fixed in v1.6, LoopBack consistently uses the singular
17+
* form now.
18+
*
19+
* Turn this option on to enable the old behaviour.
20+
*
21+
* - `app.remotes()` and `app.remoteObjects()` will be indexed using
22+
* plural names (Users instead of User).
23+
*
24+
* - Remote hooks must use plural names for the class name, i.e
25+
* `Users.create` instead of `User.create`. This is transparently
26+
* handled by `Model.beforeRemote()` and `Model.afterRemote()`.
27+
*
28+
* @type {boolean}
29+
* @deprecated Your application should not depend on the way how loopback models
30+
* and strong-remoting are wired together. It if does, you should update
31+
* it to use singular model names.
32+
*/
33+
34+
compat.usePluralNamesForRemoting = false;
35+
36+
/**
37+
* Get the class name to use with strong-remoting.
38+
* @param {function} Ctor Model class (constructor), e.g. `User`
39+
* @return {string} Singular or plural name, depending on the value
40+
* of `compat.usePluralNamesForRemoting`
41+
* @internal
42+
*/
43+
44+
compat.getClassNameForRemoting = function(Ctor) {
45+
assert(
46+
typeof(Ctor) === 'function',
47+
'compat.getClassNameForRemoting expects a constructor as the argument');
48+
49+
if (compat.usePluralNamesForRemoting) {
50+
assert(Ctor.pluralModelName,
51+
'Model must have a "pluralModelName" property in compat mode');
52+
return Ctor.pluralModelName;
53+
}
54+
55+
return Ctor.modelName;
56+
};

lib/loopback.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ loopback.version = require('../package.json').version;
4040

4141
loopback.mime = express.mime;
4242

43+
/*!
44+
* Compatibility layer, intentionally left undocumented.
45+
*/
46+
loopback.compat = require('./compat');
47+
4348
/**
4449
* Create an loopback application.
4550
*

lib/models/model.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Module Dependencies.
33
*/
44
var loopback = require('../loopback');
5+
var compat = require('../compat');
56
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
67
var modeler = new ModelBuilder();
78
var assert = require('assert');
@@ -69,7 +70,8 @@ Model.setup = function () {
6970
var self = this;
7071
if(this.app) {
7172
var remotes = this.app.remotes();
72-
remotes.before(self.pluralModelName + '.' + name, function (ctx, next) {
73+
var className = compat.getClassNameForRemoting(self);
74+
remotes.before(className + '.' + name, function (ctx, next) {
7375
fn(ctx, ctx.result, next);
7476
});
7577
} else {
@@ -85,7 +87,8 @@ Model.setup = function () {
8587
var self = this;
8688
if(this.app) {
8789
var remotes = this.app.remotes();
88-
remotes.after(self.pluralModelName + '.' + name, function (ctx, next) {
90+
var className = compat.getClassNameForRemoting(self);
91+
remotes.after(className + '.' + name, function (ctx, next) {
8992
fn(ctx, ctx.result, next);
9093
});
9194
} else {

lib/models/user.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ User.prototype.verify = function (options, fn) {
240240
options.protocol
241241
+ '://'
242242
+ options.host
243-
+ (User.sharedCtor.http.path || '/' + User.pluralModelName)
243+
+ User.http.path
244244
+ User.confirm.http.path;
245245

246246

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"dependencies": {
1717
"debug": "~0.7.2",
1818
"express": "~3.4.0",
19-
"strong-remoting": "~1.1.0",
19+
"strong-remoting": "~1.2.1",
2020
"inflection": "~1.2.5",
2121
"passport": "~0.1.17",
2222
"passport-local": "~0.1.6",
@@ -29,10 +29,10 @@
2929
"async": "~0.2.9"
3030
},
3131
"peerDependencies": {
32-
"loopback-datasource-juggler": "~1.2.11"
32+
"loopback-datasource-juggler": "~1.2.13"
3333
},
3434
"devDependencies": {
35-
"loopback-datasource-juggler": "~1.2.11",
35+
"loopback-datasource-juggler": "~1.2.13",
3636
"mocha": "~1.14.0",
3737
"strong-task-emitter": "0.0.x",
3838
"supertest": "~0.8.1",

test/app.test.js

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,52 @@ var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
44
describe('app', function() {
55

66
describe('app.model(Model)', function() {
7+
var app, db;
8+
beforeEach(function() {
9+
app = loopback();
10+
db = loopback.createDataSource({connector: loopback.Memory});
11+
});
12+
713
it("Expose a `Model` to remote clients", function() {
8-
var app = loopback();
9-
var memory = loopback.createDataSource({connector: loopback.Memory});
10-
var Color = memory.createModel('color', {name: String});
14+
var Color = db.createModel('color', {name: String});
15+
app.model(Color);
16+
17+
expect(app.models()).to.eql([Color]);
18+
});
19+
20+
it('uses singlar name as app.remoteObjects() key', function() {
21+
var Color = db.createModel('color', {name: String});
22+
app.model(Color);
23+
expect(app.remoteObjects()).to.eql({ color: Color });
24+
});
25+
26+
it('uses singular name as shared class name', function() {
27+
var Color = db.createModel('color', {name: String});
1128
app.model(Color);
12-
assert.equal(app.models().length, 1);
29+
expect(app.remotes().exports).to.eql({ color: Color });
30+
});
31+
32+
describe('in compat mode', function() {
33+
before(function() {
34+
loopback.compat.usePluralNamesForRemoting = true;
35+
});
36+
after(function() {
37+
loopback.compat.usePluralNamesForRemoting = false;
38+
});
39+
40+
it('uses plural name as shared class name', function() {
41+
loopback.compat.usePluralNamesForRemoting = true;
42+
var Color = db.createModel('color', {name: String});
43+
app.model(Color);
44+
expect(app.remotes().exports).to.eql({ colors: Color });
45+
});
46+
47+
it('uses plural name as app.remoteObjects() key', function() {
48+
var Color = db.createModel('color', {name: String});
49+
app.model(Color);
50+
expect(app.remoteObjects()).to.eql({ colors: Color });
51+
});
52+
;
1353
});
1454
});
1555

test/model.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,37 @@ describe('Model', function() {
426426
});
427427
});
428428
})
429+
430+
describe('in compat mode', function() {
431+
before(function() {
432+
loopback.compat.usePluralNamesForRemoting = true;
433+
});
434+
after(function() {
435+
loopback.compat.usePluralNamesForRemoting = false;
436+
});
437+
438+
it('correctly install before/after hooks', function(done) {
439+
var hooksCalled = [];
440+
441+
User.beforeRemote('**', function(ctx, user, next) {
442+
hooksCalled.push('beforeRemote');
443+
next();
444+
});
445+
446+
User.afterRemote('**', function(ctx, user, next) {
447+
hooksCalled.push('afterRemote');
448+
next();
449+
});
450+
451+
request(app).get('/users')
452+
.expect(200, function(err, res) {
453+
if (err) return done(err);
454+
expect(hooksCalled, 'hooks called')
455+
.to.eql(['beforeRemote', 'afterRemote']);
456+
done();
457+
});
458+
});
459+
});
429460
});
430461

431462
describe('Model.hasMany(Model)', function() {

0 commit comments

Comments
 (0)