setRemoting for relation methods when setup

This commit is contained in:
jannyHou 2016-08-26 00:00:33 -04:00
parent cc95860c68
commit 63e7a01fcf
1 changed files with 197 additions and 147 deletions

View File

@ -233,36 +233,8 @@ module.exports = function(registry) {
});
};
// resolve relation functions
sharedClass.resolve(function resolver(define) {
var relations = ModelCtor.relations || {};
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.belongsToRemoting(relationName, relation, define);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.hasOneRemoting(relationName, relation, define);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany') {
ModelCtor.hasManyRemoting(relationName, relation, define);
}
}
// handle scopes
var scopes = ModelCtor.scopes || {};
/* eslint-disable one-var */
for (var scopeName in scopes) {
ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
}
/* eslint-enable one-var */
});
// setRemoting for relation methods
ModelCtor.setupRemotingRelation();
return ModelCtor;
};
@ -444,131 +416,196 @@ module.exports = function(registry) {
this.sharedClass.disableMethod(name, isStatic || false);
this.emit('remoteMethodDisabled', this.sharedClass, name);
};
Model.belongsToRemoting = function(relationName, relation, define) {
var modelName = relation.modelTo && relation.modelTo.modelName;
modelName = modelName || 'PersistedModel';
var fn = this.prototype[relationName];
var pathName = (relation.options.http && relation.options.http.path) || relationName;
define('__get__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
accessType: 'READ',
description: format('Fetches belongsTo relation %s.', relationName),
returns: { arg: relationName, type: modelName, root: true },
}, fn);
};
Model.setupRemotingRelation = function() {
var ModelCtor = this;
var typeName = ModelCtor.modelName;
var options = ModelCtor.settings;
function convertNullToNotFoundError(toModelName, ctx, cb) {
if (ctx.result !== null) return cb();
var relations = ModelCtor.settings.relations || {};
var fk = ctx.getArgByName('fk');
var msg = g.f('Unknown "%s" id "%s".', toModelName, fk);
var error = new Error(msg);
error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND';
cb(error);
}
// get the relations
for (var relationName in relations) {
var relation = relations[relationName];
if (relation.type === 'belongsTo') {
ModelCtor.prototype['__get__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
belongsToRemoting(relationName, relation);
} else if (
relation.type === 'hasOne' ||
relation.type === 'embedsOne'
) {
ModelCtor.prototype['__get__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, '__get__' + relationName);
};
ModelCtor.prototype['__create__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, '__create__' + relationName);
};
ModelCtor.prototype['__update__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, '__update__' + relationName);
};
ModelCtor.prototype['__destroy__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, '__destory__' + relationName);
};
hasOneRemoting(relationName, relation);
} else if (
relation.type === 'hasMany' ||
relation.type === 'embedsMany' ||
relation.type === 'referencesMany'
) {
ModelCtor.prototype['__findById__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
ModelCtor.prototype['__destroyById__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
ModelCtor.prototype['__updateById__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
hasManyRemoting(relationName, relation);
if (relation.through || relation.type === 'referencesMany') {
var modelThrough = relation.through || relation.model;
Model.hasOneRemoting = function(relationName, relation, define) {
var pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelTo.modelName;
var accepts = [];
if (relation.type === 'hasMany' && relation.through) {
// Restrict: only hasManyThrough relation can have additional properties
accepts.push({ arg: 'data', type: modelThrough.modelName, http: { source: 'body' }});
}
ModelCtor.prototype['__link__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
ModelCtor.prototype['__unlink__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
ModelCtor.prototype['__exists__' + relationName] = function(data, callback) {
throwNotAttached(this.modelName, relationName);
};
throughOrReferencesRemoting(relationName, relation);
}
}
};
define('__get__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
description: format('Fetches hasOne relation %s.', relationName),
accessType: 'READ',
returns: { arg: relationName, type: relation.modelTo.modelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
});
function setRemoting(name, options) {
var fn = ModelCtor.prototype[name];
fn._delegate = true;
ModelCtor.remoteMethod(name, options);
};
define('__create__' + relationName, {
isStatic: false,
http: { verb: 'post', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: format('Creates a new instance in %s of this model.', relationName),
accessType: 'WRITE',
returns: { arg: 'data', type: toModelName, root: true },
});
function belongsToRemoting(relationName, relation) {
relation.options = relation.options || {};
var modelName = relation.model;
var pathName = (relation.options.http && relation.options.http.path) || relationName;
define('__update__' + relationName, {
isStatic: false,
http: { verb: 'put', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: format('Update %s of this model.', relationName),
accessType: 'WRITE',
returns: { arg: 'data', type: toModelName, root: true },
});
setRemoting('__get__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
accessType: 'READ',
description: format('Fetches belongsTo relation %s.', relationName),
returns: { arg: relationName, type: modelName, root: true },
});
};
define('__destroy__' + relationName, {
isStatic: false,
http: { verb: 'delete', path: '/' + pathName },
description: format('Deletes %s of this model.', relationName),
accessType: 'WRITE',
});
};
function convertNullToNotFoundError(toModelName, ctx, cb) {
if (ctx.result !== null) return cb();
Model.hasManyRemoting = function(relationName, relation, define) {
var pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelTo.modelName;
var fk = ctx.getArgByName('fk');
var msg = g.f('Unknown "%s" id "%s".', toModelName, fk);
var error = new Error(msg);
error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND';
cb(error);
};
var findByIdFunc = this.prototype['__findById__' + relationName];
define('__findById__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: { source: 'path' }},
description: format('Find a related item by id for %s.', relationName),
accessType: 'READ',
returns: { arg: 'result', type: toModelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
}, findByIdFunc);
function hasOneRemoting(relationName, relation) {
relation.options = relation.options || {};
var pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelName;
var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
define('__destroyById__' + relationName, {
isStatic: false,
http: { verb: 'delete', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: { source: 'path' }},
description: format('Delete a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: [],
}, destroyByIdFunc);
setRemoting('__get__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
description: format('Fetches hasOne relation %s.', relationName),
accessType: 'READ',
returns: { arg: relationName, type: toModelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
});
var updateByIdFunc = this.prototype['__updateById__' + relationName];
define('__updateById__' + relationName, {
isStatic: false,
http: { verb: 'put', path: '/' + pathName + '/:fk' },
accepts: [
{ arg: 'fk', type: 'any',
setRemoting('__create__' + relationName, {
isStatic: false,
http: { verb: 'post', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: format('Creates a new instance in %s of this model.', relationName),
accessType: 'WRITE',
returns: { arg: 'data', type: toModelName, root: true },
});
setRemoting('__update__' + relationName, {
isStatic: false,
http: { verb: 'put', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
description: format('Update %s of this model.', relationName),
accessType: 'WRITE',
returns: { arg: 'data', type: toModelName, root: true },
});
setRemoting('__destroy__' + relationName, {
isStatic: false,
http: { verb: 'delete', path: '/' + pathName },
description: format('Deletes %s of this model.', relationName),
accessType: 'WRITE',
});
};
function hasManyRemoting(relationName, relation) {
relation.options = relation.options || {};
var pathName = (relation.options.http && relation.options.http.path) || relationName;
var toModelName = relation.modelName;
setRemoting('__findById__' + relationName, {
isStatic: false,
http: { verb: 'get', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: { source: 'path' }},
{ arg: 'data', type: toModelName, http: { source: 'body' }},
],
description: format('Update a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: { arg: 'result', type: toModelName, root: true },
}, updateByIdFunc);
description: format('Find a related item by id for %s.', relationName),
accessType: 'READ',
returns: { arg: 'result', type: toModelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
});
setRemoting('__destroyById__' + relationName, {
isStatic: false,
http: { verb: 'delete', path: '/' + pathName + '/:fk' },
accepts: { arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: { source: 'path' }},
description: format('Delete a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: [],
});
setRemoting('__updateById__' + relationName, {
isStatic: false,
http: { verb: 'put', path: '/' + pathName + '/:fk' },
accepts: [
{ arg: 'fk', type: 'any',
description: format('Foreign key for %s', relationName),
required: true,
http: { source: 'path' }},
{ arg: 'data', type: toModelName, http: { source: 'body' }},
],
description: format('Update a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: { arg: 'result', type: toModelName, root: true },
});
};
if (relation.modelThrough || relation.type === 'referencesMany') {
var modelThrough = relation.modelThrough || relation.modelTo;
var accepts = [];
if (relation.type === 'hasMany' && relation.modelThrough) {
// Restrict: only hasManyThrough relation can have additional properties
accepts.push({ arg: 'data', type: modelThrough.modelName, http: { source: 'body' }});
}
var addFunc = this.prototype['__link__' + relationName];
define('__link__' + relationName, {
function throughOrReferenceRemoting(relationName, relation) {
setRemoting('__link__' + relationName, {
isStatic: false,
http: { verb: 'put', path: '/' + pathName + '/rel/:fk' },
accepts: [{ arg: 'fk', type: 'any',
@ -578,10 +615,9 @@ module.exports = function(registry) {
description: format('Add a related item by id for %s.', relationName),
accessType: 'WRITE',
returns: { arg: relationName, type: modelThrough.modelName, root: true },
}, addFunc);
});
var removeFunc = this.prototype['__unlink__' + relationName];
define('__unlink__' + relationName, {
setRemoting('__unlink__' + relationName, {
isStatic: false,
http: { verb: 'delete', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any',
@ -591,12 +627,11 @@ module.exports = function(registry) {
description: format('Remove the %s relation to an item by id.', relationName),
accessType: 'WRITE',
returns: [],
}, removeFunc);
});
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
// true --> 200 and false --> 404?
var existsFunc = this.prototype['__exists__' + relationName];
define('__exists__' + relationName, {
setRemoting('__exists__' + relationName, {
isStatic: false,
http: { verb: 'head', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any',
@ -622,8 +657,23 @@ module.exports = function(registry) {
}
},
},
}, existsFunc);
}
});
};
// // setRemoting for scope methods
// var scopes = ModelCtor.scopes || {};
// /* eslint-disable one-var */
// for (var scopeName in scopes) {
// ModelCtor.scopeRemoting(scopeName, scopes[scopeName], define);
// }
// /* eslint-enable one-var */
function throwNotAttached(modelName, methodName) {
throw new Error(
g.f('Cannot call %s.%s().' +
' The %s method has not been setup.' +
' The {{Model}} has not been correctly attached to a {{DataSource}}!',
modelName, methodName, methodName)
);
};
};
Model.scopeRemoting = function(scopeName, scope, define) {