diff --git a/lib/registry.js b/lib/registry.js index 7757022e..80efd1a1 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -121,7 +121,7 @@ Registry.prototype.createModel = function(name, properties, options) { var model = BaseModel.extend(name, properties, options); model.registry = this; - this._defineRemoteMethods(model, options.methods); + this._defineRemoteMethods(model, model.settings.methods); return model; }; @@ -244,6 +244,15 @@ Registry.prototype.configureModel = function(ModelCtor, config) { modelName); } + var newMethodNames = config.methods && Object.keys(config.methods); + var hasNewMethods = newMethodNames && newMethodNames.length; + var hasDescendants = this.getModelByType(ModelCtor) !== ModelCtor; + if (hasNewMethods && hasDescendants) { + g.warn( + 'Child models of `%s` will not inherit newly defined remote methods %s.', + modelName, newMethodNames); + } + // Remote methods this._defineRemoteMethods(ModelCtor, config.methods); }; diff --git a/test/loopback.test.js b/test/loopback.test.js index ad1e023c..08d2de1a 100644 --- a/test/loopback.test.js +++ b/test/loopback.test.js @@ -7,6 +7,9 @@ var it = require('./util/it'); var describe = require('./util/describe'); var Domain = require('domain'); var EventEmitter = require('events').EventEmitter; +var loopback = require('../'); +var expect = require('chai').expect; +var assert = require('assert'); describe('loopback', function() { var nameCounter = 0; @@ -608,4 +611,133 @@ describe('loopback', function() { expect(methodNames).to.include('prototype.instanceMethod'); }); }); + + describe('Remote method inheritance', function() { + var app; + + beforeEach(setupLoopback); + + it('inherits remote methods defined via createModel', function() { + var Base = app.registry.createModel('Base', {}, { + methods: { + greet: { + http: { path: '/greet' }, + }, + }, + }); + + var MyCustomModel = app.registry.createModel('MyCustomModel', {}, { + base: 'Base', + methods: { + hello: { + http: { path: '/hello' }, + }, + }, + }); + var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); + + expect(methodNames).to.include('greet'); + expect(methodNames).to.include('hello'); + }); + + it('same remote method with different metadata should override parent', function() { + var Base = app.registry.createModel('Base', {}, { + methods: { + greet: { + http: { path: '/greet' }, + }, + }, + }); + + var MyCustomModel = app.registry.createModel('MyCustomModel', {}, { + base: 'Base', + methods: { + greet: { + http: { path: '/hello' }, + }, + }, + }); + var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); + var baseMethod = Base.sharedClass.findMethodByName('greet'); + var customMethod = MyCustomModel.sharedClass.findMethodByName('greet'); + + // Base Method + expect(baseMethod.http).to.eql({ path: '/greet' }); + expect(baseMethod.http.path).to.equal('/greet'); + expect(baseMethod.http.path).to.not.equal('/hello'); + + // Custom Method + expect(methodNames).to.include('greet'); + expect(customMethod.http).to.eql({ path: '/hello' }); + expect(customMethod.http.path).to.equal('/hello'); + expect(customMethod.http.path).to.not.equal('/greet'); + }); + + it('does not inherit remote methods defined via configureModel', function() { + var Base = app.registry.createModel('Base'); + app.registry.configureModel(Base, { + dataSource: null, + methods: { + greet: { + http: { path: '/greet' }, + }, + }, + }); + + var MyCustomModel = app.registry.createModel('MyCustomModel', {}, { + base: 'Base', + methods: { + hello: { + http: { path: '/hello' }, + }, + }, + }); + var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); + + expect(methodNames).to.not.include('greet'); + expect(methodNames).to.include('hello'); + }); + + it('does not inherit remote methods defined via configureModel after child model ' + + 'was created', function() { + var Base = app.registry.createModel('Base'); + var MyCustomModel = app.registry.createModel('MyCustomModel', {}, { + base: 'Base', + }); + + app.registry.configureModel(Base, { + dataSource: null, + methods: { + greet: { + http: { path: '/greet' }, + }, + }, + }); + + app.registry.configureModel(MyCustomModel, { + dataSource: null, + methods: { + hello: { + http: { path: '/hello' }, + }, + }, + }); + var baseMethodNames = getAllMethodNamesWithoutClassName(Base); + var methodNames = getAllMethodNamesWithoutClassName(MyCustomModel); + + expect(baseMethodNames).to.include('greet'); + expect(methodNames).to.not.include('greet'); + expect(methodNames).to.include('hello'); + }); + + function setupLoopback() { + app = loopback({ localRegistry: true }); + } + + function getAllMethodNamesWithoutClassName(Model) { + return Model.sharedClass.methods().map(function(m) { + return m.stringName.replace(/^[^.]+\./, ''); // drop the class name + }); + } + }); });