Fix the model name for hasMany/through relation

This commit is contained in:
Raymond Feng 2014-11-17 09:44:20 -08:00
parent b0fc88b333
commit 4c7c8901ff
3 changed files with 178 additions and 26 deletions

View File

@ -457,7 +457,7 @@ Model.hasManyRemoting = function(relationName, relation, define) {
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
description: 'Delete a related item by id for ' + relationName,
returns: {}
returns: []
}, destroyByIdFunc);
var updateByIdFunc = this.prototype['__updateById__' + relationName];
@ -502,7 +502,7 @@ Model.hasManyRemoting = function(relationName, relation, define) {
description: 'Foreign key for ' + relationName, required: true,
http: {source: 'path'}},
description: 'Remove the ' + relationName + ' relation to an item by id',
returns: {}
returns: []
}, removeFunc);
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
@ -542,6 +542,15 @@ Model.scopeRemoting = function(scopeName, scope, define) {
var isStatic = scope.isStatic;
var toModelName = scope.modelTo.modelName;
// https://github.com/strongloop/loopback/issues/811
// Check if the scope is for a hasMany relation
var relation = this.relations[scopeName];
if (relation && relation.modelTo) {
// For a relation with through model, the toModelName should be the one
// from the target model
toModelName = relation.modelTo.modelName;
}
define('__get__' + scopeName, {
isStatic: isStatic,
http: {verb: 'get', path: '/' + pathName},

View File

@ -411,8 +411,8 @@ describe('relations - integration', function () {
});
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/rel/:fk', function () {
it('should succeed with statusCode 200', function () {
assert.equal(this.res.statusCode, 200);
it('should succeed with statusCode 204', function () {
assert.equal(this.res.statusCode, 204);
});
it('should remove the record in appointment', function (done) {
@ -469,8 +469,8 @@ describe('relations - integration', function () {
});
lt.describe.whenCalledRemotely('DELETE', '/api/physicians/:id/patients/:fk', function () {
it('should succeed with statusCode 200', function () {
assert.equal(this.res.statusCode, 200);
it('should succeed with statusCode 204', function () {
assert.equal(this.res.statusCode, 204);
});
it('should remove the record in appointment', function (done) {

View File

@ -66,33 +66,176 @@ describe('remoting - integration', function () {
});
});
describe('Model', function() {
it('has expected remote methods', function() {
var storeClass = app.handler('rest').adapter
describe('Model shared classes', function() {
function formatReturns(m) {
var returns = m.returns;
if (!returns || returns.length === 0) {
return '';
}
var type = returns[0].type;
return type ? ':' + type : '';
}
function formatMethod(m) {
return [
m.name,
'(',
m.accepts.map(function(a) {
return a.arg + ':' + a.type
}).join(','),
')',
formatReturns(m),
' ',
m.getHttpMethod(),
' ',
m.getFullPath()
].join('');
}
function findClass(name) {
return app.handler('rest').adapter
.getClasses()
.filter(function(c) { return c.name === 'store'; })[0];
.filter(function(c) {
return c.name === name;
})[0];
}
it('has expected remote methods', function() {
var storeClass = findClass('store');
var methods = storeClass.methods
.filter(function(m) {
return m.name.indexOf('__') === -1;
})
.map(function(m) {
return [
m.name + '()',
m.getHttpMethod(),
m.getFullPath()
].join(' ');
return formatMethod(m);
});
var expectedMethods = [
'create(data:object):store POST /stores',
'upsert(data:object):store PUT /stores',
'exists(id:any):boolean GET /stores/:id/exists',
'findById(id:any):store GET /stores/:id',
'find(filter:object):store GET /stores',
'findOne(filter:object):store GET /stores/findOne',
'updateAll(where:object,data:object) POST /stores/update',
'deleteById(id:any) DELETE /stores/:id',
'count(where:object):number GET /stores/count',
'prototype.updateAttributes(data:object):store PUT /stores/:id'
];
// The list of methods is from docs:
// http://docs.strongloop.com/display/LB/Exposing+models+over+a+REST+API
expect(methods).to.include.members([
'create() POST /stores',
'upsert() PUT /stores',
'exists() GET /stores/:id/exists',
'findById() GET /stores/:id',
'find() GET /stores',
'findOne() GET /stores/findOne',
'deleteById() DELETE /stores/:id',
'count() GET /stores/count',
'prototype.updateAttributes() PUT /stores/:id',
]);
expect(methods).to.include.members(expectedMethods);
});
it('has expected remote methods for scopes', function() {
var storeClass = findClass('store');
var methods = storeClass.methods
.filter(function(m) {
return m.name.indexOf('__') === 0;
})
.map(function(m) {
return formatMethod(m);
});
var expectedMethods = [
'__get__superStores(filter:object):store GET /stores/superStores',
'__create__superStores(data:store):store POST /stores/superStores',
'__delete__superStores() DELETE /stores/superStores',
'__count__superStores(where:object):number GET /stores/superStores/count'
];
expect(methods).to.include.members(expectedMethods);
});
it('should have correct signatures for belongsTo methods',
function() {
var widgetClass = findClass('widget');
var methods = widgetClass.methods
.filter(function(m) {
return m.name.indexOf('prototype.__') === 0;
})
.map(function(m) {
return formatMethod(m);
});
var expectedMethods = [
'prototype.__get__store(refresh:boolean):store ' +
'GET /widgets/:id/store'
];
expect(methods).to.include.members(expectedMethods);
});
it('should have correct signatures for hasMany methods',
function() {
var physicianClass = findClass('store');
var methods = physicianClass.methods
.filter(function(m) {
return m.name.indexOf('prototype.__') === 0;
})
.map(function(m) {
return formatMethod(m);
});
var expectedMethods = [
'prototype.__findById__widgets(fk:any):widget ' +
'GET /stores/:id/widgets/:fk',
'prototype.__destroyById__widgets(fk:any) ' +
'DELETE /stores/:id/widgets/:fk',
'prototype.__updateById__widgets(fk:any,data:widget):widget ' +
'PUT /stores/:id/widgets/:fk',
'prototype.__get__widgets(filter:object):widget ' +
'GET /stores/:id/widgets',
'prototype.__create__widgets(data:widget):widget ' +
'POST /stores/:id/widgets',
'prototype.__delete__widgets() ' +
'DELETE /stores/:id/widgets',
'prototype.__count__widgets(where:object):number ' +
'GET /stores/:id/widgets/count'
];
expect(methods).to.include.members(expectedMethods);
});
it('should have correct signatures for hasMany-through methods',
function() {
var physicianClass = findClass('physician');
var methods = physicianClass.methods
.filter(function(m) {
return m.name.indexOf('prototype.__') === 0;
})
.map(function(m) {
return formatMethod(m);
});
var expectedMethods = [
'prototype.__findById__patients(fk:any):patient ' +
'GET /physicians/:id/patients/:fk',
'prototype.__destroyById__patients(fk:any) ' +
'DELETE /physicians/:id/patients/:fk',
'prototype.__updateById__patients(fk:any,data:patient):patient ' +
'PUT /physicians/:id/patients/:fk',
'prototype.__link__patients(fk:any,data:appointment):appointment ' +
'PUT /physicians/:id/patients/rel/:fk',
'prototype.__unlink__patients(fk:any) ' +
'DELETE /physicians/:id/patients/rel/:fk',
'prototype.__exists__patients(fk:any):boolean ' +
'HEAD /physicians/:id/patients/rel/:fk',
'prototype.__get__patients(filter:object):patient ' +
'GET /physicians/:id/patients',
'prototype.__create__patients(data:patient):patient ' +
'POST /physicians/:id/patients',
'prototype.__delete__patients() ' +
'DELETE /physicians/:id/patients',
'prototype.__count__patients(where:object):number ' +
'GET /physicians/:id/patients/count'
];
expect(methods).to.include.members(expectedMethods);
});
});
});