Clean up scope implementation

This commit is contained in:
Raymond Feng 2014-06-16 10:50:42 -07:00
parent 2db43c58e5
commit 046816191d
1 changed files with 132 additions and 76 deletions

View File

@ -6,6 +6,52 @@ var defineCachedRelations = utils.defineCachedRelations;
*/ */
exports.defineScope = defineScope; exports.defineScope = defineScope;
function ScopeDefinition(definition) {
this.sourceModel = definition.sourceModel;
this.targetModel = definition.targetModel || definition.sourceModel;
this.name = definition.name;
this.params = definition.params;
this.methods = definition.methods;
}
ScopeDefinition.prototype.related = function(receiver, scopeParams, condOrRefresh, cb) {
var name = this.name;
var self = receiver;
var actualCond = {};
var actualRefresh = false;
var saveOnCache = true;
if (arguments.length === 3) {
cb = condOrRefresh;
} else if (arguments.length === 4) {
if (typeof condOrRefresh === 'boolean') {
actualRefresh = condOrRefresh;
} else {
actualCond = condOrRefresh;
actualRefresh = true;
saveOnCache = false;
}
} else {
throw new Error('Method can be only called with one or two arguments');
}
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or refresh is required
var params = mergeParams(actualCond, scopeParams);
return this.targetModel.find(params, function (err, data) {
if (!err && saveOnCache) {
defineCachedRelations(self);
self.__cachedRelations[name] = data;
}
cb(err, data);
});
} else {
// Return from cache
cb(null, self.__cachedRelations[name]);
}
}
/** /**
* Define a scope to the class * Define a scope to the class
* @param {Model} cls The class where the scope method is added * @param {Model} cls The class where the scope method is added
@ -22,7 +68,7 @@ function defineScope(cls, targetClass, name, params, methods) {
cls._scopeMeta = {}; cls._scopeMeta = {};
} }
// only makes sence to add scope in meta if base and target classes // only makes sense to add scope in meta if base and target classes
// are same // are same
if (cls === targetClass) { if (cls === targetClass) {
cls._scopeMeta[name] = params; cls._scopeMeta[name] = params;
@ -32,6 +78,14 @@ function defineScope(cls, targetClass, name, params, methods) {
} }
} }
var definition = new ScopeDefinition({
sourceModel: cls,
targetModel: targetClass,
name: name,
params: params,
methods: methods
});
// Define a property for the scope // Define a property for the scope
Object.defineProperty(cls, name, { Object.defineProperty(cls, name, {
enumerable: false, enumerable: false,
@ -49,42 +103,18 @@ function defineScope(cls, targetClass, name, params, methods) {
*/ */
get: function () { get: function () {
var self = this; var self = this;
var f = function caller(condOrRefresh, cb) { var f = function(condOrRefresh, cb) {
var actualCond = {}; if(arguments.length === 1) {
var actualRefresh = false; definition.related(self, f._scope, condOrRefresh);
var saveOnCache = true;
if (arguments.length === 1) {
cb = condOrRefresh;
} else if (arguments.length === 2) {
if (typeof condOrRefresh === 'boolean') {
actualRefresh = condOrRefresh;
} else { } else {
actualCond = condOrRefresh; definition.related(self, f._scope, condOrRefresh, cb);
actualRefresh = true;
saveOnCache = false;
}
} else {
throw new Error('Method can be only called with one or two arguments');
}
if (!self.__cachedRelations || self.__cachedRelations[name] === undefined
|| actualRefresh) {
// It either doesn't hit the cache or reresh is required
var params = mergeParams(actualCond, caller._scope);
return targetClass.find(params, function (err, data) {
if (!err && saveOnCache) {
defineCachedRelations(self);
self.__cachedRelations[name] = data;
}
cb(err, data);
});
} else {
// Return from cache
cb(null, self.__cachedRelations[name]);
} }
}; };
f._scope = typeof params === 'function' ? params.call(this) : params;
f._targetClass = targetClass.modelName; f._scope = typeof definition.params === 'function' ?
definition.params.call(self) : definition.params;
f._targetClass = definition.targetModel.modelName;
if (f._scope.collect) { if (f._scope.collect) {
f._targetClass = i8n.capitalize(f._scope.collect); f._targetClass = i8n.capitalize(f._scope.collect);
} }
@ -92,11 +122,14 @@ function defineScope(cls, targetClass, name, params, methods) {
f.build = build; f.build = build;
f.create = create; f.create = create;
f.destroyAll = destroyAll; f.destroyAll = destroyAll;
for (var i in methods) { for (var i in definition.methods) {
f[i] = methods[i].bind(this); f[i] = definition.methods[i].bind(self);
} }
// define sub-scopes // Define scope-chaining, such as
// Station.scope('active', {where: {isActive: true}});
// Station.scope('subway', {where: {isUndeground: true}});
// Station.active.subway(cb);
Object.keys(targetClass._scopeMeta).forEach(function (name) { Object.keys(targetClass._scopeMeta).forEach(function (name) {
Object.defineProperty(f, name, { Object.defineProperty(f, name, {
enumerable: false, enumerable: false,
@ -105,7 +138,7 @@ function defineScope(cls, targetClass, name, params, methods) {
return f; return f;
} }
}); });
}.bind(this)); }.bind(self));
return f; return f;
} }
}); });
@ -152,7 +185,13 @@ function defineScope(cls, targetClass, name, params, methods) {
// and it should have create/build methods with binded thisModelNameId param // and it should have create/build methods with binded thisModelNameId param
function build(data) { function build(data) {
return new targetClass(mergeParams(this._scope, {where: data || {}}).where); data = data || {};
var params = mergeParams(this._scope, {where: data}).where;
delete params['and'];
delete params['or'];
delete params['nor'];
return new targetClass(params);
} }
function create(data, cb) { function create(data, cb) {
@ -187,12 +226,44 @@ function defineScope(cls, targetClass, name, params, methods) {
} }
}); });
} }
}
function mergeParams(base, update) { /*!
* Merge `base` and `update` params
* @param {Object} base - base object (updating this object)
* @param {Object} update - object with new data to update base
* @returns {Object} `base`
* @private
*/
function mergeWhere(base, update) {
base = base || {};
if (update) {
var keys = Object.keys(update);
for (var k = 0; k < keys.length; k++) {
var key = keys[k];
base[key] = update[key];
}
}
return base;
}
/*!
* Merge query parameters
* @param base
* @param update
* @returns {*|{}}
* @private
*/
function mergeParams(base, update) {
if (!update) {
return;
}
base = base || {}; base = base || {};
if (update.where) { if (update.where) {
base.where = merge(base.where, update.where); base.where = mergeWhere(base.where, update.where);
} }
// Overwrite inclusion
if (update.include) { if (update.include) {
base.include = update.include; base.include = update.include;
} }
@ -205,36 +276,21 @@ function defineScope(cls, targetClass, name, params, methods) {
base.order = update.order; base.order = update.order;
} }
if(update.limit !== undefined) { // overwrite pagination
if (update.limit !== undefined) {
base.limit = update.limit; base.limit = update.limit;
} }
if(update.skip !== undefined) { if (update.skip !== undefined) {
base.skip = update.skip; base.skip = update.skip;
} }
if(update.offset !== undefined) { if (update.offset !== undefined) {
base.offset = update.offset; base.offset = update.offset;
} }
if(update.fields !== undefined) {
// Overwrite fields
if (update.fields !== undefined) {
base.fields = update.fields; base.fields = update.fields;
} }
return base; return base;
}
}
/**
* Merge `base` and `update` params
* @param {Object} base - base object (updating this object)
* @param {Object} update - object with new data to update base
* @returns {Object} `base`
*/
function merge(base, update) {
base = base || {};
if (update) {
Object.keys(update).forEach(function (key) {
base[key] = update[key];
});
}
return base;
} }