Merge pull request #151 from strongloop/feature/improve-remoting-metadata

Improve remoting metadata: register exported models using singular names
This commit is contained in:
Miroslav Bajtoš 2014-01-27 01:38:42 -08:00
commit fa0f402cb2
8 changed files with 150 additions and 13 deletions

View File

@ -4,6 +4,7 @@
var DataSource = require('loopback-datasource-juggler').DataSource var DataSource = require('loopback-datasource-juggler').DataSource
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder , ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
, compat = require('./compat')
, assert = require('assert') , assert = require('assert')
, fs = require('fs') , fs = require('fs')
, RemoteObjects = require('strong-remoting') , RemoteObjects = require('strong-remoting')
@ -97,8 +98,9 @@ app.disuse = function (route) {
app.model = function (Model, config) { app.model = function (Model, config) {
if(arguments.length === 1) { if(arguments.length === 1) {
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor'); assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
assert(Model.pluralModelName, 'Model must have a "pluralModelName" property'); assert(Model.modelName, 'Model must have a "modelName" property');
this.remotes().exports[Model.pluralModelName] = Model; var remotingClassName = compat.getClassNameForRemoting(Model);
this.remotes().exports[remotingClassName] = Model;
this.models().push(Model); this.models().push(Model);
Model.shared = true; Model.shared = true;
Model.app = this; Model.app = this;
@ -203,7 +205,7 @@ app.remoteObjects = function () {
models.forEach(function (ModelCtor) { models.forEach(function (ModelCtor) {
// only add shared models // only add shared models
if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') { if(ModelCtor.shared && typeof ModelCtor.sharedCtor === 'function') {
result[ModelCtor.pluralModelName] = ModelCtor; result[compat.getClassNameForRemoting(ModelCtor)] = ModelCtor;
} }
}); });

56
lib/compat.js Normal file
View File

@ -0,0 +1,56 @@
var assert = require('assert');
/**
* Compatibility layer allowing applications based on an older LoopBack version
* to work with newer versions with minimum changes involved.
*
* You should not use it unless migrating from an older version of LoopBack.
*/
var compat = exports;
/**
* LoopBack versions pre-1.6 use plural model names when registering shared
* classes with strong-remoting. As the result, strong-remoting use method names
* like `Users.create` for the javascript methods like `User.create`.
* This has been fixed in v1.6, LoopBack consistently uses the singular
* form now.
*
* Turn this option on to enable the old behaviour.
*
* - `app.remotes()` and `app.remoteObjects()` will be indexed using
* plural names (Users instead of User).
*
* - Remote hooks must use plural names for the class name, i.e
* `Users.create` instead of `User.create`. This is transparently
* handled by `Model.beforeRemote()` and `Model.afterRemote()`.
*
* @type {boolean}
* @deprecated Your application should not depend on the way how loopback models
* and strong-remoting are wired together. It if does, you should update
* it to use singular model names.
*/
compat.usePluralNamesForRemoting = false;
/**
* Get the class name to use with strong-remoting.
* @param {function} Ctor Model class (constructor), e.g. `User`
* @return {string} Singular or plural name, depending on the value
* of `compat.usePluralNamesForRemoting`
* @internal
*/
compat.getClassNameForRemoting = function(Ctor) {
assert(
typeof(Ctor) === 'function',
'compat.getClassNameForRemoting expects a constructor as the argument');
if (compat.usePluralNamesForRemoting) {
assert(Ctor.pluralModelName,
'Model must have a "pluralModelName" property in compat mode');
return Ctor.pluralModelName;
}
return Ctor.modelName;
};

View File

@ -40,6 +40,11 @@ loopback.version = require('../package.json').version;
loopback.mime = express.mime; loopback.mime = express.mime;
/*!
* Compatibility layer, intentionally left undocumented.
*/
loopback.compat = require('./compat');
/** /**
* Create an loopback application. * Create an loopback application.
* *

View File

@ -2,6 +2,7 @@
* Module Dependencies. * Module Dependencies.
*/ */
var loopback = require('../loopback'); var loopback = require('../loopback');
var compat = require('../compat');
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder; var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
var modeler = new ModelBuilder(); var modeler = new ModelBuilder();
var assert = require('assert'); var assert = require('assert');
@ -69,7 +70,8 @@ Model.setup = function () {
var self = this; var self = this;
if(this.app) { if(this.app) {
var remotes = this.app.remotes(); var remotes = this.app.remotes();
remotes.before(self.pluralModelName + '.' + name, function (ctx, next) { var className = compat.getClassNameForRemoting(self);
remotes.before(className + '.' + name, function (ctx, next) {
fn(ctx, ctx.result, next); fn(ctx, ctx.result, next);
}); });
} else { } else {
@ -85,7 +87,8 @@ Model.setup = function () {
var self = this; var self = this;
if(this.app) { if(this.app) {
var remotes = this.app.remotes(); var remotes = this.app.remotes();
remotes.after(self.pluralModelName + '.' + name, function (ctx, next) { var className = compat.getClassNameForRemoting(self);
remotes.after(className + '.' + name, function (ctx, next) {
fn(ctx, ctx.result, next); fn(ctx, ctx.result, next);
}); });
} else { } else {

View File

@ -240,7 +240,7 @@ User.prototype.verify = function (options, fn) {
options.protocol options.protocol
+ '://' + '://'
+ options.host + options.host
+ (User.sharedCtor.http.path || '/' + User.pluralModelName) + User.http.path
+ User.confirm.http.path; + User.confirm.http.path;

View File

@ -16,7 +16,7 @@
"dependencies": { "dependencies": {
"debug": "~0.7.2", "debug": "~0.7.2",
"express": "~3.4.0", "express": "~3.4.0",
"strong-remoting": "~1.1.0", "strong-remoting": "~1.2.1",
"inflection": "~1.2.5", "inflection": "~1.2.5",
"passport": "~0.1.17", "passport": "~0.1.17",
"passport-local": "~0.1.6", "passport-local": "~0.1.6",
@ -29,10 +29,10 @@
"async": "~0.2.9" "async": "~0.2.9"
}, },
"peerDependencies": { "peerDependencies": {
"loopback-datasource-juggler": "~1.2.11" "loopback-datasource-juggler": "~1.2.13"
}, },
"devDependencies": { "devDependencies": {
"loopback-datasource-juggler": "~1.2.11", "loopback-datasource-juggler": "~1.2.13",
"mocha": "~1.14.0", "mocha": "~1.14.0",
"strong-task-emitter": "0.0.x", "strong-task-emitter": "0.0.x",
"supertest": "~0.8.1", "supertest": "~0.8.1",

View File

@ -4,12 +4,52 @@ var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-app');
describe('app', function() { describe('app', function() {
describe('app.model(Model)', function() { describe('app.model(Model)', function() {
var app, db;
beforeEach(function() {
app = loopback();
db = loopback.createDataSource({connector: loopback.Memory});
});
it("Expose a `Model` to remote clients", function() { it("Expose a `Model` to remote clients", function() {
var app = loopback(); var Color = db.createModel('color', {name: String});
var memory = loopback.createDataSource({connector: loopback.Memory});
var Color = memory.createModel('color', {name: String});
app.model(Color); app.model(Color);
assert.equal(app.models().length, 1);
expect(app.models()).to.eql([Color]);
});
it('uses singlar name as app.remoteObjects() key', function() {
var Color = db.createModel('color', {name: String});
app.model(Color);
expect(app.remoteObjects()).to.eql({ color: Color });
});
it('uses singular name as shared class name', function() {
var Color = db.createModel('color', {name: String});
app.model(Color);
expect(app.remotes().exports).to.eql({ color: Color });
});
describe('in compat mode', function() {
before(function() {
loopback.compat.usePluralNamesForRemoting = true;
});
after(function() {
loopback.compat.usePluralNamesForRemoting = false;
});
it('uses plural name as shared class name', function() {
loopback.compat.usePluralNamesForRemoting = true;
var Color = db.createModel('color', {name: String});
app.model(Color);
expect(app.remotes().exports).to.eql({ colors: Color });
});
it('uses plural name as app.remoteObjects() key', function() {
var Color = db.createModel('color', {name: String});
app.model(Color);
expect(app.remoteObjects()).to.eql({ colors: Color });
});
;
}); });
}); });

View File

@ -426,6 +426,37 @@ describe('Model', function() {
}); });
}); });
}) })
describe('in compat mode', function() {
before(function() {
loopback.compat.usePluralNamesForRemoting = true;
});
after(function() {
loopback.compat.usePluralNamesForRemoting = false;
});
it('correctly install before/after hooks', function(done) {
var hooksCalled = [];
User.beforeRemote('**', function(ctx, user, next) {
hooksCalled.push('beforeRemote');
next();
});
User.afterRemote('**', function(ctx, user, next) {
hooksCalled.push('afterRemote');
next();
});
request(app).get('/users')
.expect(200, function(err, res) {
if (err) return done(err);
expect(hooksCalled, 'hooks called')
.to.eql(['beforeRemote', 'afterRemote']);
done();
});
});
});
}); });
describe('Model.hasMany(Model)', function() { describe('Model.hasMany(Model)', function() {