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 // setRemoting for relation methods
sharedClass.resolve(function resolver(define) { ModelCtor.setupRemotingRelation();
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 */
});
return ModelCtor; return ModelCtor;
}; };
@ -444,131 +416,196 @@ module.exports = function(registry) {
this.sharedClass.disableMethod(name, isStatic || false); this.sharedClass.disableMethod(name, isStatic || false);
this.emit('remoteMethodDisabled', this.sharedClass, name); this.emit('remoteMethodDisabled', this.sharedClass, name);
}; };
Model.belongsToRemoting = function(relationName, relation, define) { Model.setupRemotingRelation = function() {
var modelName = relation.modelTo && relation.modelTo.modelName; var ModelCtor = this;
modelName = modelName || 'PersistedModel'; var typeName = ModelCtor.modelName;
var fn = this.prototype[relationName]; var options = ModelCtor.settings;
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);
};
function convertNullToNotFoundError(toModelName, ctx, cb) { var relations = ModelCtor.settings.relations || {};
if (ctx.result !== null) return cb();
var fk = ctx.getArgByName('fk'); // get the relations
var msg = g.f('Unknown "%s" id "%s".', toModelName, fk); for (var relationName in relations) {
var error = new Error(msg); var relation = relations[relationName];
error.statusCode = error.status = 404; if (relation.type === 'belongsTo') {
error.code = 'MODEL_NOT_FOUND'; ModelCtor.prototype['__get__' + relationName] = function(data, callback) {
cb(error); 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 accepts = [];
var pathName = (relation.options.http && relation.options.http.path) || relationName; if (relation.type === 'hasMany' && relation.through) {
var toModelName = relation.modelTo.modelName; // 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, { function setRemoting(name, options) {
isStatic: false, var fn = ModelCtor.prototype[name];
http: { verb: 'get', path: '/' + pathName }, fn._delegate = true;
accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }}, ModelCtor.remoteMethod(name, options);
description: format('Fetches hasOne relation %s.', relationName), };
accessType: 'READ',
returns: { arg: relationName, type: relation.modelTo.modelName, root: true },
rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
});
define('__create__' + relationName, { function belongsToRemoting(relationName, relation) {
isStatic: false, relation.options = relation.options || {};
http: { verb: 'post', path: '/' + pathName }, var modelName = relation.model;
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, var pathName = (relation.options.http && relation.options.http.path) || relationName;
description: format('Creates a new instance in %s of this model.', relationName),
accessType: 'WRITE',
returns: { arg: 'data', type: toModelName, root: true },
});
define('__update__' + relationName, { setRemoting('__get__' + relationName, {
isStatic: false, isStatic: false,
http: { verb: 'put', path: '/' + pathName }, http: { verb: 'get', path: '/' + pathName },
accepts: { arg: 'data', type: toModelName, http: { source: 'body' }}, accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
description: format('Update %s of this model.', relationName), accessType: 'READ',
accessType: 'WRITE', description: format('Fetches belongsTo relation %s.', relationName),
returns: { arg: 'data', type: toModelName, root: true }, returns: { arg: relationName, type: modelName, root: true },
}); });
};
define('__destroy__' + relationName, { function convertNullToNotFoundError(toModelName, ctx, cb) {
isStatic: false, if (ctx.result !== null) return cb();
http: { verb: 'delete', path: '/' + pathName },
description: format('Deletes %s of this model.', relationName),
accessType: 'WRITE',
});
};
Model.hasManyRemoting = function(relationName, relation, define) { var fk = ctx.getArgByName('fk');
var pathName = (relation.options.http && relation.options.http.path) || relationName; var msg = g.f('Unknown "%s" id "%s".', toModelName, fk);
var toModelName = relation.modelTo.modelName; var error = new Error(msg);
error.statusCode = error.status = 404;
error.code = 'MODEL_NOT_FOUND';
cb(error);
};
var findByIdFunc = this.prototype['__findById__' + relationName]; function hasOneRemoting(relationName, relation) {
define('__findById__' + relationName, { relation.options = relation.options || {};
isStatic: false, var pathName = (relation.options.http && relation.options.http.path) || relationName;
http: { verb: 'get', path: '/' + pathName + '/:fk' }, var toModelName = relation.modelName;
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);
var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; setRemoting('__get__' + relationName, {
define('__destroyById__' + relationName, { isStatic: false,
isStatic: false, http: { verb: 'get', path: '/' + pathName },
http: { verb: 'delete', path: '/' + pathName + '/:fk' }, accepts: { arg: 'refresh', type: 'boolean', http: { source: 'query' }},
accepts: { arg: 'fk', type: 'any', description: format('Fetches hasOne relation %s.', relationName),
description: format('Foreign key for %s', relationName), accessType: 'READ',
required: true, returns: { arg: relationName, type: toModelName, root: true },
http: { source: 'path' }}, rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
description: format('Delete a related item by id for %s.', relationName), });
accessType: 'WRITE',
returns: [],
}, destroyByIdFunc);
var updateByIdFunc = this.prototype['__updateById__' + relationName]; setRemoting('__create__' + relationName, {
define('__updateById__' + relationName, { isStatic: false,
isStatic: false, http: { verb: 'post', path: '/' + pathName },
http: { verb: 'put', path: '/' + pathName + '/:fk' }, accepts: { arg: 'data', type: toModelName, http: { source: 'body' }},
accepts: [ description: format('Creates a new instance in %s of this model.', relationName),
{ arg: 'fk', type: 'any', 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), description: format('Foreign key for %s', relationName),
required: true, required: true,
http: { source: 'path' }}, http: { source: 'path' }},
{ arg: 'data', type: toModelName, http: { source: 'body' }}, description: format('Find a related item by id for %s.', relationName),
], accessType: 'READ',
description: format('Update a related item by id for %s.', relationName), returns: { arg: 'result', type: toModelName, root: true },
accessType: 'WRITE', rest: { after: convertNullToNotFoundError.bind(null, toModelName) },
returns: { arg: 'result', type: toModelName, root: true }, });
}, updateByIdFunc); 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') { function throughOrReferenceRemoting(relationName, relation) {
var modelThrough = relation.modelThrough || relation.modelTo; setRemoting('__link__' + relationName, {
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, {
isStatic: false, isStatic: false,
http: { verb: 'put', path: '/' + pathName + '/rel/:fk' }, http: { verb: 'put', path: '/' + pathName + '/rel/:fk' },
accepts: [{ arg: 'fk', type: 'any', accepts: [{ arg: 'fk', type: 'any',
@ -578,10 +615,9 @@ module.exports = function(registry) {
description: format('Add a related item by id for %s.', relationName), description: format('Add a related item by id for %s.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: { arg: relationName, type: modelThrough.modelName, root: true }, returns: { arg: relationName, type: modelThrough.modelName, root: true },
}, addFunc); });
var removeFunc = this.prototype['__unlink__' + relationName]; setRemoting('__unlink__' + relationName, {
define('__unlink__' + relationName, {
isStatic: false, isStatic: false,
http: { verb: 'delete', path: '/' + pathName + '/rel/:fk' }, http: { verb: 'delete', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any', 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), description: format('Remove the %s relation to an item by id.', relationName),
accessType: 'WRITE', accessType: 'WRITE',
returns: [], returns: [],
}, removeFunc); });
// FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD? // FIXME: [rfeng] How to map a function with callback(err, true|false) to HEAD?
// true --> 200 and false --> 404? // true --> 200 and false --> 404?
var existsFunc = this.prototype['__exists__' + relationName]; setRemoting('__exists__' + relationName, {
define('__exists__' + relationName, {
isStatic: false, isStatic: false,
http: { verb: 'head', path: '/' + pathName + '/rel/:fk' }, http: { verb: 'head', path: '/' + pathName + '/rel/:fk' },
accepts: { arg: 'fk', type: 'any', 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) { Model.scopeRemoting = function(scopeName, scope, define) {