From 046816191df587e686ebd5e6c0c7e330159c643e Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Mon, 16 Jun 2014 10:50:42 -0700 Subject: [PATCH] Clean up scope implementation --- lib/scope.js | 208 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 132 insertions(+), 76 deletions(-) diff --git a/lib/scope.js b/lib/scope.js index ac14efc7..185ad0d3 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -6,6 +6,52 @@ var defineCachedRelations = utils.defineCachedRelations; */ 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 * @param {Model} cls The class where the scope method is added @@ -22,7 +68,7 @@ function defineScope(cls, targetClass, name, params, methods) { 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 if (cls === targetClass) { 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 Object.defineProperty(cls, name, { enumerable: false, @@ -49,42 +103,18 @@ function defineScope(cls, targetClass, name, params, methods) { */ get: function () { var self = this; - var f = function caller(condOrRefresh, cb) { - var actualCond = {}; - var actualRefresh = false; - var saveOnCache = true; - if (arguments.length === 1) { - cb = condOrRefresh; - } else if (arguments.length === 2) { - if (typeof condOrRefresh === 'boolean') { - actualRefresh = condOrRefresh; - } else { - actualCond = condOrRefresh; - actualRefresh = true; - saveOnCache = false; - } + var f = function(condOrRefresh, cb) { + if(arguments.length === 1) { + definition.related(self, f._scope, condOrRefresh); } 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]); + definition.related(self, f._scope, condOrRefresh, cb); } }; - 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) { f._targetClass = i8n.capitalize(f._scope.collect); } @@ -92,11 +122,14 @@ function defineScope(cls, targetClass, name, params, methods) { f.build = build; f.create = create; f.destroyAll = destroyAll; - for (var i in methods) { - f[i] = methods[i].bind(this); + for (var i in definition.methods) { + 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.defineProperty(f, name, { enumerable: false, @@ -105,7 +138,7 @@ function defineScope(cls, targetClass, name, params, methods) { return f; } }); - }.bind(this)); + }.bind(self)); return f; } }); @@ -152,7 +185,13 @@ function defineScope(cls, targetClass, name, params, methods) { // and it should have create/build methods with binded thisModelNameId param 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) { @@ -187,53 +226,70 @@ function defineScope(cls, targetClass, name, params, methods) { } }); } - - function mergeParams(base, update) { - base = base || {}; - if (update.where) { - base.where = merge(base.where, update.where); - } - if (update.include) { - base.include = update.include; - } - if (update.collect) { - base.collect = update.collect; - } - - // overwrite order - if (update.order) { - base.order = update.order; - } - - if(update.limit !== undefined) { - base.limit = update.limit; - } - if(update.skip !== undefined) { - base.skip = update.skip; - } - if(update.offset !== undefined) { - base.offset = update.offset; - } - if(update.fields !== undefined) { - base.fields = update.fields; - } - 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` + * @private */ -function merge(base, update) { +function mergeWhere(base, update) { base = base || {}; if (update) { - Object.keys(update).forEach(function (key) { + 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 || {}; + if (update.where) { + base.where = mergeWhere(base.where, update.where); + } + + // Overwrite inclusion + if (update.include) { + base.include = update.include; + } + if (update.collect) { + base.collect = update.collect; + } + + // overwrite order + if (update.order) { + base.order = update.order; + } + + // overwrite pagination + if (update.limit !== undefined) { + base.limit = update.limit; + } + if (update.skip !== undefined) { + base.skip = update.skip; + } + if (update.offset !== undefined) { + base.offset = update.offset; + } + + // Overwrite fields + if (update.fields !== undefined) { + base.fields = update.fields; } return base; }