text: 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]);
  	}
  
  	if (path_str[0] !== path.sep) {
  		path_str = 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;
  }
  