2013-04-11 23:23:34 +00:00
|
|
|
/**
|
|
|
|
* Module exports
|
|
|
|
*/
|
|
|
|
exports.defineScope = defineScope;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Scope mixin for ./model.js
|
|
|
|
*/
|
2013-05-28 05:20:30 +00:00
|
|
|
var DataAccessObject = require('./dao.js');
|
2013-04-11 23:23:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Define scope
|
|
|
|
* TODO: describe behavior and usage examples
|
|
|
|
*/
|
2013-05-28 05:20:30 +00:00
|
|
|
DataAccessObject.scope = function (name, params) {
|
2013-04-11 23:23:34 +00:00
|
|
|
defineScope(this, this, name, params);
|
|
|
|
};
|
|
|
|
|
|
|
|
function defineScope(cls, targetClass, name, params, methods) {
|
|
|
|
|
|
|
|
// collect meta info about scope
|
|
|
|
if (!cls._scopeMeta) {
|
|
|
|
cls._scopeMeta = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// only makes sence to add scope in meta if base and target classes
|
|
|
|
// are same
|
|
|
|
if (cls === targetClass) {
|
|
|
|
cls._scopeMeta[name] = params;
|
|
|
|
} else {
|
|
|
|
if (!targetClass._scopeMeta) {
|
|
|
|
targetClass._scopeMeta = {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.defineProperty(cls, name, {
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
get: function () {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw new Error('Method can be only called with one or two arguments');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.__cachedRelations || (typeof this.__cachedRelations[name] == 'undefined') || actualRefresh) {
|
|
|
|
var self = this;
|
2013-04-12 21:35:06 +00:00
|
|
|
var params = mergeParams(actualCond, caller._scope);
|
|
|
|
return targetClass.all(params, function(err, data) {
|
2013-04-11 23:23:34 +00:00
|
|
|
if (!err && saveOnCache) {
|
2013-04-11 23:56:51 +00:00
|
|
|
if (!self.__cachedRelations) {
|
|
|
|
self.__cachedRelations = {};
|
|
|
|
}
|
2013-04-11 23:23:34 +00:00
|
|
|
self.__cachedRelations[name] = data;
|
|
|
|
}
|
|
|
|
cb(err, data);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
cb(null, this.__cachedRelations[name]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
f._scope = typeof params === 'function' ? params.call(this) : params;
|
|
|
|
f.build = build;
|
|
|
|
f.create = create;
|
|
|
|
f.destroyAll = destroyAll;
|
|
|
|
for (var i in methods) {
|
|
|
|
f[i] = methods[i].bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// define sub-scopes
|
|
|
|
Object.keys(targetClass._scopeMeta).forEach(function (name) {
|
|
|
|
Object.defineProperty(f, name, {
|
|
|
|
enumerable: false,
|
|
|
|
get: function () {
|
|
|
|
mergeParams(f._scope, targetClass._scopeMeta[name]);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}.bind(this));
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// and it should have create/build methods with binded thisModelNameId param
|
|
|
|
function build(data) {
|
|
|
|
return new targetClass(mergeParams(this._scope, {where:data || {}}).where);
|
|
|
|
}
|
|
|
|
|
|
|
|
function create(data, cb) {
|
|
|
|
if (typeof data === 'function') {
|
|
|
|
cb = data;
|
|
|
|
data = {};
|
|
|
|
}
|
|
|
|
this.build(data).save(cb);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Callback
|
|
|
|
- The callback will be called after all elements are destroyed
|
|
|
|
- For every destroy call which results in an error
|
|
|
|
- If fetching the Elements on which destroyAll is called results in an error
|
|
|
|
*/
|
|
|
|
function destroyAll(cb) {
|
|
|
|
targetClass.all(this._scope, function (err, data) {
|
|
|
|
if (err) {
|
|
|
|
cb(err);
|
|
|
|
} else {
|
|
|
|
(function loopOfDestruction (data) {
|
|
|
|
if(data.length > 0) {
|
|
|
|
data.shift().destroy(function(err) {
|
|
|
|
if(err && cb) cb(err);
|
|
|
|
loopOfDestruction(data);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if(cb) cb();
|
|
|
|
}
|
|
|
|
}(data));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function mergeParams(base, update) {
|
|
|
|
if (update.where) {
|
|
|
|
base.where = merge(base.where, update.where);
|
|
|
|
}
|
2013-04-12 21:35:06 +00:00
|
|
|
if (update.include) {
|
|
|
|
base.include = update.include;
|
|
|
|
}
|
|
|
|
if (update.collect) {
|
|
|
|
base.collect = update.collect;
|
|
|
|
}
|
2013-04-11 23:23:34 +00:00
|
|
|
|
|
|
|
// overwrite order
|
|
|
|
if (update.order) {
|
|
|
|
base.order = update.order;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|