UNPKG

orm

Version:

NodeJS Object-relational mapping

332 lines (293 loc) 9.5 kB
var _ = require("lodash"); var async = require("async"); var Utilities = require("./Utilities"); var ChainInstance = require("./ChainInstance"); var Promise = require("bluebird"); var DeprecatedPromise = require("./DeprecatedPromise").Promise; module.exports = ChainFind; function ChainFind(Model, opts) { var promiseFunctionPostfix = Model.settings.get('promiseFunctionPostfix'); var prepareConditions = function () { return Utilities.transformPropertyNames( opts.conditions, opts.properties ); }; var prepareOrder = function () { return Utilities.transformOrderPropertyNames( opts.order, opts.properties ); }; var chainRun = function (done) { var order, conditions; conditions = Utilities.transformPropertyNames( opts.conditions, opts.properties ); order = Utilities.transformOrderPropertyNames( opts.order, opts.properties ); opts.driver.find(opts.only, opts.table, conditions, { limit : opts.limit, order : order, merge : opts.merge, offset : opts.offset, exists : opts.exists }, function (err, dataItems) { if (err) { return done(err); } if (dataItems.length === 0) { return done(null, []); } var eagerLoad = function (err, items) { var idMap = {}; var keys = _.map(items, function (item, index) { var key = item[opts.keys[0]]; // Create the association arrays for (var i = 0, association; association = opts.__eager[i]; i++) { item[association.name] = []; } idMap[key] = index; return key; }); async.eachSeries(opts.__eager, function (association, cb) { opts.driver.eagerQuery(association, opts, keys, function (err, instances) { if (err) return cb(err) for (var i = 0, instance; instance = instances[i]; i++) { // Perform a parent lookup with $p, and initialize it as an instance. items[idMap[instance.$p]][association.name].push(association.model(instance)); } cb(); }); }, function (err) { if (err) done(err); else done(null, items); } ); }; async.map(dataItems, opts.newInstance, function (err, items) { if (err) return done(err); var shouldEagerLoad = opts.__eager && opts.__eager.length; var completeFn = shouldEagerLoad ? eagerLoad : done; return completeFn(null, items); }); }); }; var promise = null; var chain = { find: function () { var cb = null; var args = Array.prototype.slice.call(arguments); opts.conditions = opts.conditions || {}; if (typeof _.last(args) === "function") { cb = args.pop(); } if (typeof args[0] === "object") { _.extend(opts.conditions, args[0]); } else if (typeof args[0] === "string") { opts.conditions.__sql = opts.conditions.__sql || []; opts.conditions.__sql.push(args); } if (cb) { chainRun(cb); } return this; }, only: function () { if (arguments.length && Array.isArray(arguments[0])) { opts.only = arguments[0]; } else { opts.only = Array.prototype.slice.apply(arguments); } return this; }, omit: function () { var omit = null; if (arguments.length && Array.isArray(arguments[0])) { omit = arguments[0]; } else { omit = Array.prototype.slice.apply(arguments); } this.only(_.difference(Object.keys(opts.properties), omit)); return this; }, limit: function (limit) { opts.limit = limit; return this; }, skip: function (offset) { return this.offset(offset); }, offset: function (offset) { opts.offset = offset; return this; }, order: function (property, order) { if (!Array.isArray(opts.order)) { opts.order = []; } if (property[0] === "-") { opts.order.push([property.substr(1), "Z"]); } else { opts.order.push([property, (order && order.toUpperCase() === "Z" ? "Z" : "A")]); } return this; }, orderRaw: function (str, args) { if (!Array.isArray(opts.order)) { opts.order = []; } opts.order.push([str, args || []]); return this; }, count: function (cb) { opts.driver.count(opts.table, prepareConditions(), { merge : opts.merge }, function (err, data) { if (err || data.length === 0) { return cb(err); } return cb(null, data[0].c); }); return this; }, remove: function (cb) { var keys = _.map(opts.keyProperties, 'mapsTo'); opts.driver.find(keys, opts.table, prepareConditions(), { limit : opts.limit, order : prepareOrder(), merge : opts.merge, offset : opts.offset, exists : opts.exists }, function (err, data) { if (err) { return cb(err); } if (data.length === 0) { return cb(null); } var ids = [], conditions = {}; var or; conditions.or = []; for (var i = 0; i < data.length; i++) { or = {}; for (var j = 0; j < opts.keys.length; j++) { or[keys[j]] = data[i][keys[j]]; } conditions.or.push(or); } return opts.driver.remove(opts.table, conditions, cb); }); return this; }, first: function (cb) { return this.run(function (err, items) { return cb(err, items && items.length > 0 ? items[0] : null); }); }, last: function (cb) { return this.run(function (err, items) { return cb(err, items && items.length > 0 ? items[items.length - 1] : null); }); }, each: function (cb) { return new ChainInstance(this, cb); }, run: function (cb) { chainRun(cb); return this; }, success: function (cb) { console.warn("ChainFind.success() function is deprecated & will be removed in a future version"); if (!promise) { promise = new DeprecatedPromise(); promise.handle(this.all); } return promise.success(cb); }, fail: function (cb) { if (!promise) { promise = new DeprecatedPromise(); promise.handle(this.all); } console.warn("ChainFind.fail() function is deprecated & will be removed in a future version"); return promise.fail(cb); }, eager: function () { // This will allow params such as ("abc", "def") or (["abc", "def"]) var associations = _.flatten(arguments); // TODO: Implement eager loading for Mongo and delete this. if (opts.driver.config.protocol == "mongodb:") { throw new Error("MongoDB does not currently support eager loading"); } opts.__eager = _.filter(opts.associations, function (association) { return ~associations.indexOf(association.name); }); return this; } }; chain.all = chain.where = chain.find; chain['find' + promiseFunctionPostfix] = Promise.promisify(chain.find); chain['all' + promiseFunctionPostfix] = Promise.promisify(chain.all); chain['where' + promiseFunctionPostfix] = Promise.promisify(chain.where); chain['first' + promiseFunctionPostfix] = Promise.promisify(chain.first); chain['last' + promiseFunctionPostfix] = Promise.promisify(chain.last); chain['run' + promiseFunctionPostfix] = Promise.promisify(chain.run); chain['remove' + promiseFunctionPostfix] = Promise.promisify(chain.remove); if (opts.associations) { for (var i = 0; i < opts.associations.length; i++) { addChainMethod(chain, opts.associations[i], opts); } } for (var k in Model) { if ([ "hasOne", "hasMany", "drop", "sync", "get", "clear", "create", "exists", "settings", "aggregate" ].indexOf(k) >= 0) { continue; } if (typeof Model[k] !== "function" || chain[k]) { continue; } chain[k] = Model[k]; } chain.model = Model; chain.options = opts; return chain; } function addChainMethod(chain, association, opts) { chain[association.hasAccessor] = function (value) { if (!opts.exists) { opts.exists = []; } var conditions = {}; var assocIds = Object.keys(association.mergeAssocId); var ids = association.model.id; function mergeConditions(source) { for (var i = 0; i < assocIds.length; i++) { if (typeof conditions[assocIds[i]] === "undefined") { conditions[assocIds[i]] = source[ids[i]]; } else if (Array.isArray(conditions[assocIds[i]])) { conditions[assocIds[i]].push(source[ids[i]]); } else { conditions[assocIds[i]] = [ conditions[assocIds[i]], source[ids[i]] ]; } } } if (Array.isArray(value)) { for (var i = 0; i < value.length; i++) { mergeConditions(value[i]); } } else { mergeConditions(value); } opts.exists.push({ table : association.mergeTable, link : [ Object.keys(association.mergeId), association.model.id ], conditions : conditions }); return chain; }; }