/*! update.js */
/**
* This updates an array of items, or generates an update compiled function from update-clause operators object.
* Since version 0.0.4, dot notation (e.g. foo.bar.baz) is supported to handle child nodes.
*
* @method obop.prototype.update
* @param {Array} [array] - source array to update
* @param {Object|Function} update - update operators or function
* @see http://docs.mongodb.org/manual/reference/operator/#update-operators
* @returns {Array} Array of updated items (if source array given)
* @returns {Function} Update function compiled for Array.prototype.map() method (if source array not given)
* @returns {Error} Error instance (if source array not given but invalid update operaters given)
*
* @example
* var list = [
* { name: "apple", price: 50 },
* { name: "orange", price: 10 },
* { name: "pineapple", price: 70 },
* { name: "grape", price: 30 }
* ];
*
* // update without obop
* var out1 = list.filter(function(item) {
* item.sale = true;
* item.price -= 5;
* return item;
* });
* console.log(out1);
*
* // update with obop
* var update = { $set: { sale: true }, $inc: { price: -5 } };
* var out2 = obop.update(list, update);
* console.log(out2);
*
* // compile a update function
* var update = { $set: { sale: true }, $inc: { price: -5 } };
* var updater = obop.update(update);
* if (updater instanceof Error) throw updater;
* var out3 = list.map(updater);
* console.log(out3);
*/
exports.update = function(array, param) {
var func;
var len = arguments.length;
if (len == 1) {
func = update.call(this, array);
if (func instanceof Error) throw func;
return func;
} else if (len == 2) {
if (array instanceof Array) {
func = update.call(this, param);
if (func instanceof Error) throw func;
return array.map(func);
} else {
throw new Error('Invalid argument type: ' + array);
}
} else {
throw new Error('Invalid arguments length: ' + len);
}
};
function update(param) {
var obop = this;
var _nop = null; // no operation
var err;
// function type
if ('function' == typeof param) {
return param;
}
// default parameters
param = param || {};
// no operators: through
if (!Object.keys(param)) {
return _nop;
}
// other types than object
if ('object' != typeof param) {
return new Error('Invalid update operator type: ' + update);
}
// parse operators
var array = [];
Object.keys(param).forEach(function(op) {
if (err) return;
var gen = obop.$update[op];
if (!gen) {
err = new Error('Unknown update operator: ' + op);
return;
}
var hash = param[op];
var root = {};
var hasdot = {};
for (var key in hash) {
var val = hash[key];
var pos = key.indexOf('.');
if (pos > -1) {
// $op: { "pre.post": "val" }
var pre = key.substr(0, pos);
var post = key.substr(pos + 1);
hasdot[pre] = hasdot[pre] || {};
hasdot[pre][post] = val;
} else {
// $op: { "key": "val" }
root[key] = val;
}
}
// $op: { "key": "val" }
if (Object.keys(root).length) {
var f = gen(root);
array.push(f);
}
// $op: { "pre.post": "val" }
if (Object.keys(hasdot).length) {
Object.keys(hasdot).forEach(function(key) {
var tmp = {};
tmp[op] = hasdot[key];
var update = obop.update(tmp);
var f = function(item) {
var hash = item[key];
if ('object' !== typeof hash) {
hash = item[key] = {};
}
item[key] = update(hash);
return item;
};
array.push(f);
});
}
});
if (err) return err;
// one operator type used
if (array.length < 2) {
return array.shift() || _nop;
}
// more operator types used
return join(array);
}
function join(array) {
var len = array.length;
return function(item) {
for (var i = 0; i < len; i++) {
var update = array[i];
item = update(item);
}
return item;
};
}