Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions lib/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,13 @@ module.exports = function(registry) {
};

Model.hasManyRemoting = function(relationName, relation, define) {
var Model = this;
var options = Model.settings;
var pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelTo.modelName;

options.replaceOnPUT = options.replaceOnPUT !== false;

var findByIdFunc = this.prototype['__findById__' + relationName];
define('__findById__' + relationName, {
isStatic: false,
Expand Down Expand Up @@ -660,10 +664,31 @@ module.exports = function(registry) {
returns: [],
}, destroyByIdFunc);

var replaceByIdFunc = this.prototype['__replaceById__' + relationName];
var replaceByIdOptions = {
isStatic: false,
http: [{verb: 'post', path: '/' + pathName + '/:fk/replace'}],
accepts: [
{arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: {source: 'path'}},
{arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
],
description: format('Replace a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: {arg: 'result', type: toModelName, root: true},
};
if (options.replaceOnPUT) {
replaceByIdOptions.http.push({verb: 'put', path: '/' + pathName + '/:fk'});
}
define('__replaceById__' + relationName, replaceByIdOptions, replaceByIdFunc);

var updateByIdFunc = this.prototype['__updateById__' + relationName];
define('__updateById__' + relationName, {
var updateByIdOptions = {
isStatic: false,
http: {verb: 'put', path: '/' + pathName + '/:fk'},
http: [{verb: 'patch', path: '/' + pathName + '/:fk'}],
accepts: [
{arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
Expand All @@ -672,10 +697,14 @@ module.exports = function(registry) {
{arg: 'data', type: 'object', model: toModelName, http: {source: 'body'}},
{arg: 'options', type: 'object', http: 'optionsFromRequest'},
],
description: format('Update a related item by id for %s.', relationName),
description: format('Patch a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: {arg: 'result', type: toModelName, root: true},
}, updateByIdFunc);
};
if (!options.replaceOnPUT) {
updateByIdOptions.http.push({verb: 'put', path: '/' + pathName + '/:fk'});
}
define('__updateById__' + relationName, updateByIdOptions, updateByIdFunc);

if (relation.modelThrough || relation.type === 'referencesMany') {
var modelThrough = relation.modelThrough || relation.modelTo;
Expand Down
4 changes: 2 additions & 2 deletions test/relations.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ describe('relations - integration', function() {
});
});

describe('PUT /api/store/:id/widgets/:fk', function() {
describe('PATCH /api/store/:id/widgets/:fk', function() {
beforeEach(function(done) {
var self = this;
this.store.widgets.create({
Expand All @@ -238,7 +238,7 @@ describe('relations - integration', function() {
});
it('does not add default properties to request body', function(done) {
var self = this;
self.request.put(self.url)
self.request.patch(self.url)
.send({active: true})
.end(function(err) {
if (err) return done(err);
Expand Down
72 changes: 67 additions & 5 deletions test/remoting.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,17 @@ describe('remoting - integration', function() {
function() {
var storeClass = findClass('store');
var methods = getFormattedPrototypeMethods(storeClass.methods);

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:object:widget):widget ' +
'prototype.__replaceById__widgets(fk:any,data:object:widget):widget ' +
'POST /stores/:id/widgets/:fk/replace',
'prototype.__replaceById__widgets(fk:any,data:object:widget):widget ' +
'PUT /stores/:id/widgets/:fk',
'prototype.__updateById__widgets(fk:any,data:object:widget):widget ' +
'PATCH /stores/:id/widgets/:fk',
'prototype.__get__widgets(filter:object):widget ' +
'GET /stores/:id/widgets',
'prototype.__create__widgets(data:object:widget):widget ' +
Expand All @@ -165,8 +168,12 @@ describe('remoting - integration', function() {
'GET /physicians/:id/patients/:fk',
'prototype.__destroyById__patients(fk:any) ' +
'DELETE /physicians/:id/patients/:fk',
'prototype.__updateById__patients(fk:any,data:object:patient):patient ' +
'prototype.__replaceById__patients(fk:any,data:object:patient):patient ' +
'POST /physicians/:id/patients/:fk/replace',
'prototype.__replaceById__patients(fk:any,data:object:patient):patient ' +
'PUT /physicians/:id/patients/:fk',
'prototype.__updateById__patients(fk:any,data:object:patient):patient ' +
'PATCH /physicians/:id/patients/:fk',
'prototype.__link__patients(fk:any,data:object:appointment):appointment ' +
'PUT /physicians/:id/patients/rel/:fk',
'prototype.__unlink__patients(fk:any) ' +
Expand Down Expand Up @@ -231,7 +238,6 @@ describe('With model.settings.replaceOnPUT false', function() {
function() {
var storeClass = findClass('storeWithReplaceOnPUTfalse');
var methods = getFormattedMethodsExcludingRelations(storeClass.methods);

var expectedMethods = [
'create(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse POST /stores-updating',
'patchOrCreate(data:object:storeWithReplaceOnPUTfalse):storeWithReplaceOnPUTfalse PUT /stores-updating',
Expand All @@ -252,9 +258,37 @@ describe('With model.settings.replaceOnPUT false', function() {
'createChangeStream(options:object):ReadableStream POST /stores-updating/change-stream',
'createChangeStream(options:object):ReadableStream GET /stores-updating/change-stream',
];

expect(methods).to.eql(expectedMethods);
});

it('should have correct signatures for hasMany methods',
function() {
var storeClass = findClass('storeWithReplaceOnPUTfalse');
var methods = getFormattedPrototypeMethods(storeClass.methods);

var expectedMethods = [
'prototype.__findById__widgets(fk:any):widget ' +
'GET /stores-updating/:id/widgets/:fk',
'prototype.__destroyById__widgets(fk:any) ' +
'DELETE /stores-updating/:id/widgets/:fk',
'prototype.__replaceById__widgets(fk:any,data:object:widget):widget ' +
'POST /stores-updating/:id/widgets/:fk/replace',
'prototype.__updateById__widgets(fk:any,data:object:widget):widget ' +
'PATCH /stores-updating/:id/widgets/:fk',
'prototype.__updateById__widgets(fk:any,data:object:widget):widget ' +
'PUT /stores-updating/:id/widgets/:fk',
'prototype.__get__widgets(filter:object):widget ' +
'GET /stores-updating/:id/widgets',
'prototype.__create__widgets(data:object:widget):widget ' +
'POST /stores-updating/:id/widgets',
'prototype.__delete__widgets() ' +
'DELETE /stores-updating/:id/widgets',
'prototype.__count__widgets(where:object):number ' +
'GET /stores-updating/:id/widgets/count',
];

expect(methods).to.include.members(expectedMethods);
});
});

describe('With model.settings.replaceOnPUT true', function() {
Expand All @@ -278,6 +312,34 @@ describe('With model.settings.replaceOnPUT true', function() {
'prototype.patchAttributes(data:object:storeWithReplaceOnPUTtrue):storeWithReplaceOnPUTtrue PATCH /stores-replacing/:id',
];

expect(methods).to.include.members(expectedMethods);
});

it('should have correct signatures for hasMany methods',
function() {
var storeClass = findClass('storeWithReplaceOnPUTtrue');
var methods = getFormattedPrototypeMethods(storeClass.methods);
var expectedMethods = [
'prototype.__findById__widgets(fk:any):widget ' +
'GET /stores-replacing/:id/widgets/:fk',
'prototype.__destroyById__widgets(fk:any) ' +
'DELETE /stores-replacing/:id/widgets/:fk',
'prototype.__replaceById__widgets(fk:any,data:object:widget):widget ' +
'POST /stores-replacing/:id/widgets/:fk/replace',
'prototype.__replaceById__widgets(fk:any,data:object:widget):widget ' +
'PUT /stores-replacing/:id/widgets/:fk',
'prototype.__updateById__widgets(fk:any,data:object:widget):widget ' +
'PATCH /stores-replacing/:id/widgets/:fk',
'prototype.__get__widgets(filter:object):widget ' +
'GET /stores-replacing/:id/widgets',
'prototype.__create__widgets(data:object:widget):widget ' +
'POST /stores-replacing/:id/widgets',
'prototype.__delete__widgets() ' +
'DELETE /stores-replacing/:id/widgets',
'prototype.__count__widgets(where:object):number ' +
'GET /stores-replacing/:id/widgets/count',
];

expect(methods).to.include.members(expectedMethods);
});
});
Expand Down