orm
Version:
NodeJS Object-relational mapping
348 lines (294 loc) • 9.63 kB
JavaScript
var _ = require('lodash')
/**
* Order should be a String (with the property name assumed ascending)
* or an Array or property String names.
*
* Examples:
*
* 1. 'property1' (ORDER BY property1 ASC)
* 2. '-property1' (ORDER BY property1 DESC)
* 3. [ 'property1' ] (ORDER BY property1 ASC)
* 4. [ '-property1' ] (ORDER BY property1 DESC)
* 5. [ 'property1', 'A' ] (ORDER BY property1 ASC)
* 6. [ 'property1', 'Z' ] (ORDER BY property1 DESC)
* 7. [ '-property1', 'A' ] (ORDER BY property1 ASC)
* 8. [ 'property1', 'property2' ] (ORDER BY property1 ASC, property2 ASC)
* 9. [ 'property1', '-property2' ] (ORDER BY property1 ASC, property2 DESC)
* ...
*/
exports.standardizeOrder = function (order) {
if (typeof order === "string") {
if (order[0] === "-") {
return [ [ order.substr(1), "Z" ] ];
}
return [ [ order, "A" ] ];
}
var new_order = [], minus;
for (var i = 0; i < order.length; i++) {
minus = (order[i][0] === "-");
if (i < order.length - 1 && [ "A", "Z" ].indexOf(order[i + 1].toUpperCase()) >= 0) {
new_order.push([
(minus ? order[i].substr(1) : order[i]),
order[i + 1]
]);
i += 1;
} else if (minus) {
new_order.push([ order[i].substr(1), "Z" ]);
} else {
new_order.push([ order[i], "A" ]);
}
}
return new_order;
};
/**
* Operations
* A) Build an index of associations, with their name as the key
* B) Check for any conditions with a key in the association index
* C) Ensure that our condition supports array values
* D) Remove original condition (not DB compatible)
* E) Convert our association fields into an array, indexes are the same as model.id
* F) Itterate through values for the condition, only accept instances of the same type as the association
*/
exports.checkConditions = function (conditions, one_associations) {
var k, i, j;
// A)
var associations = {};
for (i = 0; i < one_associations.length; i++) {
associations[one_associations[i].name] = one_associations[i];
}
for (k in conditions) {
// B)
if (!associations.hasOwnProperty(k)) continue;
// C)
var values = conditions[k];
if (!Array.isArray(values)) values = [values];
// D)
delete conditions[k];
// E)
var association_fields = Object.keys(associations[k].field);
var model = associations[k].model;
// F)
for (i = 0; i < values.length; i++) {
if (values[i].isInstance && values[i].model().uid === model.uid) {
if (association_fields.length === 1) {
if (typeof conditions[association_fields[0]] === 'undefined') {
conditions[association_fields[0]] = values[i][model.id[0]];
} else if(Array.isArray(conditions[association_fields[0]])) {
conditions[association_fields[0]].push(values[i][model.id[0]]);
} else {
conditions[association_fields[0]] = [conditions[association_fields[0]], values[i][model.id[0]]];
}
} else {
var _conds = {};
for (j = 0; j < association_fields.length; i++) {
_conds[association_fields[j]] = values[i][model.id[j]];
}
conditions.or = conditions.or || [];
conditions.or.push(_conds);
}
}
}
}
return conditions;
};
/**
* Gets all the values within an object or array, optionally
* using a keys array to get only specific values
*/
exports.values = function (obj, keys) {
var i, k, vals = [];
if (keys) {
for (i = 0; i < keys.length; i++) {
vals.push(obj[keys[i]]);
}
} else if (Array.isArray(obj)) {
for (i = 0; i < obj.length; i++) {
vals.push(obj[i]);
}
} else {
for (k in obj) {
if (!/[0-9]+/.test(k)) {
vals.push(obj[k]);
}
}
}
return vals;
};
// Qn: is Zero a valid value for a FK column?
// Why? Well I've got a pre-existing database that started all its 'serial' IDs at zero...
// Answer: hasValues() is only used in hasOne association, so it's probably ok...
exports.hasValues = function (obj, keys) {
for (var i = 0; i < keys.length; i++) {
if (!obj[keys[i]] && obj[keys[i]] !== 0) return false; // 0 is also a good value...
}
return true;
};
exports.populateConditions = function (model, fields, source, target, overwrite) {
for (var i = 0; i < model.id.length; i++) {
if (typeof target[fields[i]] === 'undefined' || overwrite !== false) {
target[fields[i]] = source[model.id[i]];
} else if (Array.isArray(target[fields[i]])) {
target[fields[i]].push(source[model.id[i]]);
} else {
target[fields[i]] = [target[fields[i]], source[model.id[i]]];
}
}
};
exports.getConditions = function (model, fields, from) {
var conditions = {};
exports.populateConditions(model, fields, from, conditions);
return conditions;
};
exports.wrapFieldObject = function (params) {
if (!params.field) {
var assoc_key = params.model.settings.get("properties.association_key");
if (typeof assoc_key === "function") {
params.field = assoc_key(params.altName.toLowerCase(), params.model.id[0]);
} else {
params.field = assoc_key.replace("{name}", params.altName.toLowerCase())
.replace("{field}", params.model.id[0]);
}
}
if (typeof params.field == 'object') {
for (var k in params.field) {
if (!/[0-9]+/.test(k) && params.field.hasOwnProperty(k)) {
return params.field;
}
}
}
var newObj = {}, newProp, propPreDefined, propFromKey;
propPreDefined = params.model.properties[params.field];
propFromKey = params.model.properties[params.model.id[0]];
newProp = { type: 'integer' };
var prop = _.cloneDeep(propPreDefined || propFromKey || newProp);
if (!propPreDefined) {
_.extend(prop, {
name: params.field, mapsTo: params.mapsTo || params.field
});
}
newObj[params.field] = prop;
return newObj;
};
exports.formatField = function (model, name, required, reversed) {
var fields = {}, field_opts, field_name;
var keys = model.id;
var assoc_key = model.settings.get("properties.association_key");
for (var i = 0; i < keys.length; i++) {
if (reversed) {
field_name = keys[i];
} else if (typeof assoc_key === "function") {
field_name = assoc_key(name.toLowerCase(), keys[i]);
} else {
field_name = assoc_key.replace("{name}", name.toLowerCase())
.replace("{field}", keys[i]);
}
if (model.properties.hasOwnProperty(keys[i])) {
var p = model.properties[keys[i]];
field_opts = {
type : p.type || "integer",
size : p.size || 4,
unsigned : p.unsigned || true,
time : p.time || false,
big : p.big || false,
values : p.values || null,
required : required,
name : field_name,
mapsTo : field_name
};
} else {
field_opts = {
type : "integer",
unsigned : true,
size : 4,
required : required,
name : field_name,
mapsTo : field_name
};
}
fields[field_name] = field_opts;
}
return fields;
};
// If the parent associations key is `serial`, the join tables
// key should be changed to `integer`.
exports.convertPropToJoinKeyProp = function (props, opts) {
var prop;
for (var k in props) {
prop = props[k];
prop.required = opts.required;
if (prop.type == 'serial') {
prop.type = 'integer';
}
if (opts.makeKey) {
prop.key = true;
} else {
delete prop.key;
}
}
return props;
}
exports.getRealPath = function (path_str, stack_index) {
var path = require("path"); // for now, load here (only when needed)
var cwd = process.cwd();
var err = new Error();
var tmp = err.stack.split(/\r?\n/)[typeof stack_index !== "undefined" ? stack_index : 3], m;
if ((m = tmp.match(/^\s*at\s+(.+):\d+:\d+$/)) !== null) {
cwd = path.dirname(m[1]);
} else if ((m = tmp.match(/^\s*at\s+module\.exports\s+\((.+?)\)/)) !== null) {
cwd = path.dirname(m[1]);
} else if ((m = tmp.match(/^\s*at\s+.+\s+\((.+):\d+:\d+\)$/)) !== null) {
cwd = path.dirname(m[1]);
}
var pathIsAbsolute = path.isAbsolute || require('path-is-absolute');
if (!pathIsAbsolute(path_str)) {
path_str = path.join(cwd, path_str);
}
if (path_str.substr(-1) === path.sep) {
path_str += "index";
}
return path_str;
};
exports.transformPropertyNames = function (dataIn, properties) {
var k, prop;
var dataOut = {};
for (k in dataIn) {
prop = properties[k];
if (prop) {
dataOut[prop.mapsTo] = dataIn[k];
} else {
dataOut[k] = dataIn[k];
}
}
return dataOut;
};
exports.transformOrderPropertyNames = function (order, properties) {
if (!order) return order;
var i, item;
var newOrder = JSON.parse(JSON.stringify(order));
// Rename order properties according to mapsTo
for (var i = 0; i < newOrder.length; i++) {
item = newOrder[i];
// orderRaw
if (Array.isArray(item[1])) continue;
if (Array.isArray(item[0])) {
// order on a hasMany
// [ ['modelName', 'propName'], 'Z']
item[0][1] = properties[item[0][1]].mapsTo;
} else {
// normal order
item[0] = properties[item[0]].mapsTo;
}
}
return newOrder;
}
exports.renameDatastoreFieldsToPropertyNames = function (data, fieldToPropertyMap) {
var k, prop;
for (k in data) {
prop = fieldToPropertyMap[k];
if (prop && prop.name != k) {
data[prop.name] = data[k];
delete data[k];
}
}
return data;
}