UNPKG

oomph

Version:

Object Oriented javascript models for the client and the server

1,169 lines (1,138 loc) 44.5 kB
// Generated by CoffeeScript 1.9.3 (function() { var Promise, ValidationError, _, _utilities, addToWriteQueue, clearUniqueQueuedIds, createIntegerSubset, createObjectFromHash, findKeywordsInAnyFields, generateId, generateUniqueId, idToCreatedAtDate, idToSeconds, indexSearchableString, indexSortedSet, numberOfExtraCharactersOnId, performValidations, pluralise, processWriteQueue, publishSubscribe, redis, redisObjectDataStore, removeIndexedSearchableString, sendAttributesForSaving, sortAlphabeticallyFn, sortAscendingNumbersFn, validate, writeAttributes, slice = [].slice; redis = require('redis'); Promise = require('promise'); pluralise = require('pluralize'); _ = require('lodash'); _utilities = require('../publicModules/utilities'); publishSubscribe = require('../publicModules/publishSubscribe'); ValidationError = require('../models/ValidationError'); numberOfExtraCharactersOnId = 2; generateId = function() { var d, s; d = new Date(); s = (+d).toString(36); return s + _utilities.randomString(numberOfExtraCharactersOnId); }; idToSeconds = function(id) { return parseInt(id.slice(0, -numberOfExtraCharactersOnId), 36); }; idToCreatedAtDate = function(id) { return new Date(idToSeconds(id)); }; sortAscendingNumbersFn = function(a, b) { return b - a; }; sortAlphabeticallyFn = function(a, b) { return a < b; }; generateUniqueId = function() { var condition, newIdPromise, self, uniqueId; self = this; uniqueId = false; condition = function() { return uniqueId === false; }; return newIdPromise = _utilities.promiseWhile(condition, function() { return new Promise(function(resolve) { var id; id = generateId(); return self.redis.lrange(self.name + '#uniqueQueuedIds', 0, -1, function(error, idsArray) { if (_.includes(idsArray, id)) { id = generateId(); return resolve(); } else { return self.redis.rpush(self.name + '#uniqueQueuedIds', id, function(error, response) { uniqueId = true; return resolve(id); }); } }); }); }); }; createObjectFromHash = function(hash, modelPrototype) { var key, obj, plainKey, propertyCastType, value; obj = {}; if (modelPrototype) { Object.create(modelPrototype); } if (!hash) { return false; } if (hash.id) { obj.createdAt = idToCreatedAtDate(hash.id); } for (key in hash) { value = hash[key]; plainKey = key.replace(/\[\w\]$/, ''); propertyCastType = key.match(/\[(\w)\]$/); if (propertyCastType) { if (propertyCastType[1] === 'b') { obj[plainKey] = value === 'true'; } else if (propertyCastType[1] === 'i') { obj[plainKey] = parseInt(value); } } else { obj[key] = value; } } return obj; }; validate = function(validationObj, attrName, attrValue) { var j, len, ref, validationPromise, validationSetting, validationType, validations; validations = []; ref = Object.keys(validationObj); for (j = 0, len = ref.length; j < len; j++) { validationType = ref[j]; validationSetting = validationObj[validationType]; if (validationType !== 'presence' && !attrValue) { continue; } switch (validationType) { case 'presence': if (_utilities.isBlank(attrValue)) { validations.push(new ValidationError(attrName + " must be present", { attribute: attrName, expected: validationSetting })); } break; case 'equalTo': if (attrValue !== validationSetting) { validations.push(new ValidationError(attrName + " should equal " + validationSetting, { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'lessThan': if (!(attrValue < validationSetting)) { validations.push(new ValidationError(attrName + " should be less than " + validationSetting, { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'lessThanOrEqualTo': if (!(attrValue <= validationSetting)) { validations.push(new ValidationError(attrName + " should be less than or equal to " + validationSetting, { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'greaterThanOrEqualTo': if (!(attrValue >= validationSetting)) { validations.push(new ValidationError(attrName + " should be greater than or equal to " + validationSetting, { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'greaterThan': if (!(attrValue > validationSetting)) { validations.push(new ValidationError(attrName + " should be greater than " + validationSetting, { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'inclusionIn': if (!_.includes(validationSetting, attrValue)) { validations.push(new ValidationError(attrName + " must be one of the accepted values", { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'exclusionIn': if (_.includes(validationSetting, attrValue)) { validations.push(new ValidationError(attrName + " must not be one of the forbidden values", { attribute: attrName, actual: attrValue, expected: validationSetting })); } break; case 'uniqueness': validationPromise = new Promise((function(_this) { return function(resolve, reject) { return _this.redis.get(_this.name + '#' + attrName + ':' + attrValue, function(error, obj) { if (error || obj) { return resolve(new ValidationError(attrName + " should be a unique value", { attribute: attrName, actual: attrValue, expected: validationSetting })); } else { return resolve(); } }); }; })(this)); validations.push(validationPromise); break; case 'length': if (validationSetting.hasOwnProperty('is')) { if (attrValue.length !== validationSetting.is) { validations.push(new ValidationError(attrName + " should have a length of " + validationSetting.is, { attribute: attrName, actual: attrValue, expected: validationSetting.is })); } } else if (validationSetting.hasOwnProperty('minimum')) { if (!(attrValue.length >= validationSetting.minimum)) { validations.push(new ValidationError(attrName + " should have a minimum length of " + validationSetting.minimum, { attribute: attrName, actual: attrValue, expected: validationSetting.minimum })); } } else if (validationSetting.hasOwnProperty('maximum')) { if (!(attrValue.length <= validationSetting.maximum)) { validations.push(new ValidationError(attrName + " should have a maximum length of " + validationSetting.maximum, { attribute: attrName, actual: attrValue, expected: validationSetting.maximum })); } } else { throw new Error("length validation setting not valid on " + attrName); } break; case 'format': if (validationSetting.hasOwnProperty('with')) { if (validationSetting["with"].exec(attrValue) === null) { validations.push(new ValidationError(attrName + " should meet the format requirements", { attribute: attrName, actual: attrValue, expected: validationSetting["with"] })); } } else if (validationSetting.hasOwnProperty('without')) { if (validationSetting.without.exec(attrValue) !== null) { validations.push(new ValidationError(attrName + " should meet the format requirements", { attribute: attrName, actual: attrValue, expected: validationSetting.without })); } } else { throw new Error("format validation setting not valid on " + attrName); } } } return Promise.all(validations); }; performValidations = function(dataFields) { var returnedValidations; if (_.isEmpty(dataFields)) { throw new Error("No valid fields given"); } returnedValidations = _.map(this.attributes, (function(_this) { return function(attrObj, attrName) { var attrValue; if (attrObj.validates) { attrValue = dataFields[attrName]; return validate.apply(_this, [attrObj.validates, attrName, attrValue]); } }; })(this)); return Promise.all(returnedValidations).then(function(validationArray) { var errors; errors = _(validationArray).flattenDeep().compact().value(); if (!_.isEmpty(errors)) { throw errors; } }); }; indexSortedSet = function(setKey, attr) { var addToTmpSetPromise, listKey, setTmpKey, sortPromise; listKey = setKey + 'TempList'; setTmpKey = setKey + 'TempList'; sortPromise = new Promise((function(_this) { return function(resolve, reject) { return _this.redis.sort(setKey, 'by', _this.name + ':*->' + attr, 'alpha', 'store', listKey, function(error, newLength) { return resolve(newLength); }); }; })(this)); addToTmpSetPromise = sortPromise.then((function(_this) { return function(listLength) { var multi; multi = _this.redis.multi(); return new Promise(function(resolve, reject) { _.times(listLength, function() { return multi.lpop(listKey); }); return multi.exec(function(error, ids) { var addToSet; addToSet = _.map(ids, function(id, i) { return new Promise(function(r) { return _this.redis.zadd(setTmpKey, listLength - i, id, function(res) { return r(); }); }); }); return resolve(Promise.all(addToSet)); }); }); }; })(this)); return addToTmpSetPromise.then((function(_this) { return function() { return new Promise(function(resolve) { return _this.redis.rename(setTmpKey, setKey, function(res) { return resolve(); }); }); }; })(this)); }; indexSearchableString = function(attr, words, id) { var char, indexPromiseFn, indexPromises, j, k, len, len1, ref, word, wordSegment, wordSegmentKey; indexPromises = []; ref = words.split(/\s/); for (j = 0, len = ref.length; j < len; j++) { word = ref[j]; word = word.toLowerCase(); wordSegment = ''; for (k = 0, len1 = word.length; k < len1; k++) { char = word[k]; wordSegment += char; wordSegmentKey = this.name + '#' + attr + '/' + wordSegment; indexPromiseFn = (function(_this) { return function(wordSegmentKey, id) { return new Promise(function(resolve) { return _this.redis.zadd(wordSegmentKey, 1, id, function(res) { return resolve(); }); }); }; })(this); indexPromises.push(indexPromiseFn(wordSegmentKey, id)); } } return Promise.all(indexPromises); }; removeIndexedSearchableString = function(attr, words, id) { var char, indexPromiseFn, indexPromises, j, k, len, len1, ref, word, wordSegment, wordSegmentKey; indexPromises = []; ref = words.split(/\s/); for (j = 0, len = ref.length; j < len; j++) { word = ref[j]; word = word.toLowerCase(); wordSegment = ''; for (k = 0, len1 = word.length; k < len1; k++) { char = word[k]; wordSegment += char; wordSegmentKey = this.name + '#' + attr + '/' + wordSegment; indexPromiseFn = (function(_this) { return function(wordSegmentKey, id) { return new Promise(function(resolve) { return _this.redis.zrem(wordSegmentKey, id, function(res) { return resolve(); }); }); }; })(this); indexPromises.push(indexPromiseFn(wordSegmentKey, id)); } } return Promise.all(indexPromises); }; writeAttributes = function(props) { var idPromise, indexPromise, newObjectFlag, self, writePromise; self = this; newObjectFlag = false; idPromise = new Promise(function(resolve) { if (!props.id) { newObjectFlag = true; return generateUniqueId.apply(self).then(function(id) { return resolve(id); }); } else { return resolve(props.id); } }); writePromise = idPromise.then(function(id) { var storableProps; props.id = id; storableProps = _.clone(props); return new Promise(function(resolve) { var attr, obj, ref; ref = self.attributes; for (attr in ref) { obj = ref[attr]; switch (obj.dataType) { case 'integer': if (storableProps[attr]) { storableProps[attr + '[i]'] = storableProps[attr]; delete storableProps[attr]; } break; case 'boolean': if (storableProps[attr]) { storableProps[attr + '[b]'] = storableProps[attr]; delete storableProps[attr]; } break; case 'reference': if (obj.many) { delete storableProps[attr]; if (newObjectFlag) { storableProps[attr] = true; } } } } return self.redis.hmset(self.name + ":" + props.id, storableProps, function(err, res) { return resolve(storableProps); }); }); }); indexPromise = writePromise.then(function(storedProps) { var attr, indexPromiseFn, indexingPromises, multi, multipleValues, namespace, obj, ref, sortedSetName, value; indexingPromises = []; multi = self.redis.multi(); indexPromiseFn = function(sortedSetName, attributeName) { var largestSortedSetSize; largestSortedSetSize = 9007199254740992; return new Promise(function(resolve) { return self.redis.zadd(sortedSetName, largestSortedSetSize, props.id, function(error, res) { return indexSortedSet.apply(self, [sortedSetName, attributeName]).then(function() { return resolve(); }); }); }); }; sortedSetName = self.name + ">id"; indexingPromises.push(indexPromiseFn(sortedSetName, "id")); ref = self.attributes; for (attr in ref) { obj = ref[attr]; if (props[attr] === void 0) { continue; } value = props[attr]; switch (obj.dataType) { case 'integer': sortedSetName = self.name + ">" + attr; multi.zadd(sortedSetName, parseInt(value), props.id); break; case 'string': if (obj.sortable) { sortedSetName = self.name + ">" + attr; indexingPromises.push(indexPromiseFn(sortedSetName, attr)); } if (obj.identifiable) { multi.set(self.name + "#" + attr + ":" + value, props.id); } if (obj.searchable) { indexingPromises.push(indexSearchableString.apply(self, [attr, value, props.id])); } break; case 'text': if (obj.searchable) { indexingPromises.push(indexSearchableString.apply(self, [attr, value, props.id])); } break; case 'boolean': if (_.includes([true, 'true', false, 'false'], value)) { multi.zadd(self.name + "#" + attr + ":" + value, 1, props.id); } break; case 'reference': namespace = obj.reverseReferenceAttribute || attr; if (obj.many) { multipleValues = _.compact(value.split(",")); multi.sadd.apply(multi, [self.name + ":" + props.id + "#" + attr + ':' + obj.referenceModelName + 'Refs'].concat(slice.call(multipleValues))); multipleValues.forEach(function(vid) { return multi.sadd(obj.referenceModelName + ":" + vid + "#" + namespace + ':' + self.name + 'Refs', props.id); }); } else { multi.sadd(obj.referenceModelName + ":" + value + "#" + namespace + ':' + self.name + 'Refs', props.id); } break; default: if (obj['dataType'] !== null) { reject(new Error("Unrecognised dataType " + obj.dataType)); } } } return new Promise(function(resolve) { return multi.exec(function() { return resolve(Promise.all(indexingPromises)); }); }); }); return indexPromise.then(function() { return props; }); }; clearUniqueQueuedIds = function() { return this.redis.del(this.name + '#uniqueQueuedIds'); }; processWriteQueue = function() { var condition, hasQueue, processPromise, self, writeReturnObject; self = this; hasQueue = true; condition = function() { return hasQueue; }; writeReturnObject = {}; processPromise = _utilities.promiseWhile(condition, function() { var writePromise; return writePromise = new Promise(function(resolve, reject) { return self.redis.rpop(self.name + "#TmpQueue", function(error, tmpId) { if (tmpId) { return self.redis.hgetall(self.name + "#TmpQueueObj:" + tmpId, function(err, props) { self.redis.del(self.name + "#TmpQueueObj:" + tmpId); if (props) { return writeAttributes.apply(self, [props]).then(function(writtenObject) { writeReturnObject[tmpId] = writtenObject; return resolve(); }); } else { return reject(new Error("No properties in Queued Object " + self.name + "#TmpQueueObj:" + tmpId)); } }); } else { clearUniqueQueuedIds.apply(self); hasQueue = false; return resolve(); } }); }); }); return processPromise.then(function() { _.each(writeReturnObject, function(obj, tmpId) { return publishSubscribe.broadcast.apply(self, ["attributes_written_" + tmpId, obj]); }); return writeObjectArray; }); }; addToWriteQueue = function(props) { var p, self, tmpId; self = this; tmpId = "TmpId" + generateId() + _utilities.randomString(12); p = new Promise(function(resolve) { return self.redis.hmset(self.name + "#TmpQueueObj:" + tmpId, props, (function(_this) { return function(err, res) { return self.redis.lpush(self.name + "#TmpQueue", tmpId, function(error, newListLength) { return resolve(tmpId); }); }; })(this)); }); return p.then(function(tmpId) { clearTimeout(self._ORMWriteQueueTimeout); self._ORMWriteQueueTimeout = setTimeout(function() { return processWriteQueue.apply(self); }, 100); return new Promise(function(resolve) { var resolveFn; resolveFn = function(obj) { publishSubscribe.removeAllListenersOn.apply(self, ["attributes_written_" + tmpId]); return resolve(obj); }; return publishSubscribe.listen.apply(self, ["attributes_written_" + tmpId, resolveFn]); }); }); }; sendAttributesForSaving = function(dataFields, skipValidation) { var attrs, props, sanitisedDataFields, validationPromise; if (skipValidation) { validationPromise = new Promise(function(resolve) { return resolve(true); }); props = dataFields; } else { attrs = _.keys(this.attributes); attrs.push("id"); sanitisedDataFields = _(dataFields).omit(_.isNull).omit(_.isUndefined).pick(attrs).value(); props = sanitisedDataFields; validationPromise = performValidations.apply(this, [props]); } return new Promise((function(_this) { return function(resolve, reject) { if (_.isEmpty(props)) { reject(new Error("Properties are empty")); } return validationPromise.then(function() { return resolve(addToWriteQueue.apply(_this, [props])); }, function(validationErrors) { return reject(validationErrors); }); }; })(this)); }; createIntegerSubset = function(integerSortedSetName, tempIntegerKey, minValue, maxValue) { var self; self = this; return new Promise(function(resolve) { return self.redis.zrangebyscore(integerSortedSetName, minValue, maxValue, function(error, resultList) { var j, len, multi, result; multi = self.redis.multi(); for (j = 0, len = resultList.length; j < len; j++) { result = resultList[j]; multi.zadd(tempIntegerKey, 0, result); multi.expire(tempIntegerKey, 1); } return multi.exec(function() { return resolve(); }); }); }); }; findKeywordsInAnyFields = function(fields, keywords, weightOptions) { var unionKeyPromises, unionkeyNames; unionkeyNames = []; unionKeyPromises = _.map(keywords, (function(_this) { return function(keyword) { var field, j, keyNames, len, unionKey, unionKeyPromise, weight; keyNames = []; for (j = 0, len = fields.length; j < len; j++) { field = fields[j]; weight = (weightOptions[field] && weightOptions[field].weight ? weightOptions[field].weight : 1); keyNames.push({ name: _this.name + "#" + field + "/" + keyword, weight: weight }); } unionKey = 'keywordUnionSet:' + _utilities.randomString(5); unionKeyPromise = new Promise(function(resolve) { var ref; return (ref = _this.redis).zunionstore.apply(ref, [unionKey, keyNames.length].concat(slice.call(_.map(keyNames, 'name')), ['weights'], slice.call(_.map(keyNames, 'weight')), [function() { return resolve(unionKey); }])); }); return unionKeyPromise.then(function(unionKey) { return unionkeyNames.push(unionKey); }); }; })(this)); return Promise.all(unionKeyPromises).then(function() { return unionkeyNames; }); }; redisObjectDataStore = { moduleName: "redisObjectDataStore", moduleInitialise: function() { return this.redis = redis.createClient(); }, all: function(args) { var allArgs; allArgs = _.pick(args, ['sortBy', 'sortDirection', 'limit', 'offset']); return this.where(allArgs); }, find: function(id) { var getHash, modifyHashPromise, referencePromises, self; self = this; referencePromises = []; getHash = new Promise(function(resolve, reject) { return self.redis.hgetall(self.name + ":" + id, function(error, hash) { if (!hash) { return reject(new Error("Not Found")); } else { return resolve(hash); } }); }); modifyHashPromise = getHash.then(function(hash) { var attrSettings, getReferenceIdsFn, hashPromiseFn, j, len, propertyName, propertyValue, ref, referencePromise; ref = Object.keys(self.attributes); for (j = 0, len = ref.length; j < len; j++) { propertyName = ref[j]; propertyValue = hash[propertyName]; attrSettings = self.attributes[propertyName]; if (_.isUndefined(propertyValue)) { continue; } if (attrSettings.dataType === 'reference') { if (attrSettings.many) { getReferenceIdsFn = function(propertyName, referenceModelName) { return new Promise(function(resolve, reject) { var referenceKey; referenceKey = self.name + ':' + id + '#' + propertyName + ':' + referenceModelName + 'Refs'; return self.redis.smembers(referenceKey, function(err, ids) { var hashObj; hashObj = { propertyName: propertyName, referenceModelName: referenceModelName }; return resolve({ ids: ids, hashObj: hashObj }); }); }); }; referencePromise = getReferenceIdsFn(propertyName, attrSettings.referenceModelName).then(function(obj) { var getObjects; getObjects = _.map(obj.ids, function(id) { return new Promise(function(resolve, reject) { return self.redis.hgetall(obj.hashObj.referenceModelName + ':' + id, function(err, hash) { return resolve(createObjectFromHash(hash)); }); }); }); return Promise.all(getObjects).then(function(arr) { obj.hashObj.referenceValue = arr; return obj.hashObj; }); }); referencePromises.push(referencePromise); } else { hashPromiseFn = function(propertyName, propertyValue, referenceModelName) { return new Promise(function(resolve, reject) { return self.redis.hgetall(referenceModelName + ':' + propertyValue, function(err, hash) { var hashObj; hashObj = { propertyName: propertyName, propertyValue: propertyValue, referenceModelName: referenceModelName }; hashObj.referenceValue = createObjectFromHash(hash); return resolve(hashObj); }); }); }; referencePromises.push(hashPromiseFn(propertyName, propertyValue, attrSettings.referenceModelName)); } delete hash[propertyName]; } } return hash; }); return modifyHashPromise.then(function(hash) { return Promise.all(referencePromises).then(function(referenceObjects) { _.each(referenceObjects, function(refObj) { return hash[refObj.propertyName] = refObj.referenceValue; }); return createObjectFromHash(hash, self.prototype); }); }); }, findBy: function(option) { var p; p = new Promise((function(_this) { return function(resolve, reject) { var condition, optionName, stringName; optionName = Object.keys(option)[0]; condition = option[optionName]; if (optionName === 'id') { return resolve(condition); } else { if (_this.attributes[optionName].dataType === 'string' && _this.attributes[optionName].identifiable) { stringName = _this.name + "#" + optionName + ":" + condition; return _this.redis.get(stringName, function(err, res) { return resolve(res); }); } else { return reject((function() { throw new Error("Not an identifier"); })()); } } }; })(this)); return p.then((function(_this) { return function(res) { return _this.find(res); }; })(this)); }, where: function(args) { var attr, end, field, fields, integerSortedSetName, j, k, keyword, keywordSearchPromise, keywords, l, len, len1, len2, len3, len4, m, maxValue, minValue, modifyObj, modifyObjAttrs, n, namespace, option, optionValue, prepareWhereConditionPromise, ref, ref1, referenceModelName, self, sortedSetKeys, start, tempIntegerKey, unionSortedSetKeys, weight, weightOptions, whereConditionPromises; self = this; args || (args = {}); args.sortBy || (args.sortBy = (args.includes ? 'relevance' : 'id')); args.sortDirection || (args.sortDirection = 'asc'); args.sortDirection.toLowerCase(); args.limit || (args.limit = null); args.offset || (args.offset = null); if (args.sortBy === 'random') { start = 0; end = -1; } else { start = +args.offset; end = args.limit > 0 ? (args.limit - 1) + args.offset : -1; } if (args.sortDirection === 'desc') { end = args.offset > 0 ? -(args.offset + 1) : -1; start = args.limit > 0 ? end - (args.limit - 1) : 0; } sortedSetKeys = []; unionSortedSetKeys = []; if (args.sortBy === 'random') { sortedSetKeys.push({ name: self.name + '>id' }); } else if (args.sortBy !== 'relevance') { sortedSetKeys.push({ name: self.name + '>' + args.sortBy }); } weightOptions = {}; keywordSearchPromise = new Promise(function(r) { return r(); }); if (args.includes) { if (args.includes.modifiedWeights) { ref = args.includes.modifiedWeights; for (j = 0, len = ref.length; j < len; j++) { modifyObj = ref[j]; modifyObjAttrs = _.isArray(modifyObj.attributes) ? modifyObj.attributes : [modifyObj.attributes]; for (k = 0, len1 = modifyObjAttrs.length; k < len1; k++) { attr = modifyObjAttrs[k]; weightOptions[attr] = {}; weightOptions[attr].weight = +modifyObj.weight; } } } fields = args.includes.inAllOf || args.includes.inAnyOf || [args.includes["in"]]; keywords = args.includes.keywords.split(/\s/); keywords = _.reject(keywords, function(s) { return s === ''; }); if (args.includes.inAnyOf) { keywordSearchPromise = findKeywordsInAnyFields.apply(self, [fields, keywords, weightOptions]).then(function(keyNames) { return _.each(keyNames, function(key) { return sortedSetKeys.push({ name: key }); }); }); } else if (args.includes.inAllOf || args.includes["in"]) { for (l = 0, len2 = fields.length; l < len2; l++) { field = fields[l]; weight = (weightOptions[field] && weightOptions[field].weight ? weightOptions[field].weight : 1); for (m = 0, len3 = keywords.length; m < len3; m++) { keyword = keywords[m]; sortedSetKeys.push({ name: self.name + "#" + field + "/" + keyword, weight: weight }); } } } } whereConditionPromises = []; ref1 = Object.keys(args); for (n = 0, len4 = ref1.length; n < len4; n++) { option = ref1[n]; optionValue = args[option]; if (!this.attributes[option]) { continue; } switch (this.attributes[option].dataType) { case 'integer': tempIntegerKey = 'temporaryIntegerSet:' + _utilities.randomString(5); integerSortedSetName = self.name + '>' + option; minValue = '-inf'; maxValue = '+inf'; if (optionValue.greaterThan) { minValue = optionValue.greaterThan + 1; maxValue = '+inf'; } if (optionValue.greaterThanOrEqualTo) { minValue = optionValue.greaterThanOrEqualTo; maxValue = '+inf'; } if (optionValue.lessThan) { minValue = '-inf'; maxValue = optionValue.lessThan - 1; } if (optionValue.lessThanOrEqualTo) { minValue = '-inf'; maxValue = optionValue.lessThanOrEqualTo; } if (optionValue.equalTo) { minValue = optionValue.equalTo; maxValue = optionValue.equalTo; } whereConditionPromises.push(new Promise(function(resolve) { return createIntegerSubset.apply(self, [integerSortedSetName, tempIntegerKey, minValue, maxValue]).then(function() { return resolve(); }); })); sortedSetKeys.push({ name: tempIntegerKey }); sortedSetKeys.push({ name: integerSortedSetName }); break; case 'boolean': sortedSetKeys.push({ name: self.name + "#" + option + ":" + optionValue }); break; case 'reference': referenceModelName = this.attributes[option].referenceModelName; if (referenceModelName) { namespace = this.attributes[option].reverseReferenceAttribute || option; if (this.attributes[option].many) { if (optionValue.includesAllOf) { _.each(optionValue.includesAllOf, function(id) { return sortedSetKeys.push({ name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs' }); }); } if (optionValue.includesAnyOf) { _.each(optionValue.includesAnyOf, function(id) { return unionSortedSetKeys.push({ name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs' }); }); } } else { if (optionValue.anyOf) { _.each(optionValue.anyOf, function(id) { return unionSortedSetKeys.push({ name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs' }); }); } else { sortedSetKeys.push({ name: referenceModelName + ':' + optionValue + '#' + namespace + ':' + self.name + 'Refs' }); } } } } } prepareWhereConditionPromise = Promise.all(whereConditionPromises).then(function() { var unionPromise; if (_.isEmpty(unionSortedSetKeys)) { return keywordSearchPromise; } else { unionPromise = new Promise(function(resolve) { var ref2, unionKey, unionSortedSetKeyNames; unionSortedSetKeyNames = _.map(unionSortedSetKeys, 'name'); unionKey = 'temporaryUnionSet:' + _utilities.randomString(24); return (ref2 = self.redis).zunionstore.apply(ref2, [unionKey, unionSortedSetKeys.length].concat(slice.call(unionSortedSetKeyNames), [function(err, numberofresults) { self.redis.expire(unionKey, 5); sortedSetKeys.push({ name: unionKey }); return resolve(); }])); }); return unionPromise.then(function() { return keywordSearchPromise; }); } }); return prepareWhereConditionPromise.then(function() { var idsKeyPromise, matchedIdsPromise; idsKeyPromise = new Promise(function(resolve) { var intersectKey, ref2, sortedSetKeyNames; intersectKey = 'temporaryIntersectSet:' + _utilities.randomString(24); sortedSetKeyNames = _.map(sortedSetKeys, 'name'); return (ref2 = self.redis).zinterstore.apply(ref2, [intersectKey, sortedSetKeys.length].concat(slice.call(sortedSetKeyNames), [function(err, numberOfResults) { self.redis.expire(intersectKey, 5); return resolve({ intersectKey: intersectKey, numberOfResults: numberOfResults }); }])); }); matchedIdsPromise = idsKeyPromise.then(function(resultObj) { var facetResults, facetsPromises, idKey, totalResults; idKey = resultObj.intersectKey; totalResults = resultObj.numberOfResults; facetResults = {}; facetsPromises = []; _.each(args.facets, function(f) { facetResults[f] = []; return facetsPromises.push(new Promise(function(resolve) { return self.redis.sort(idKey, 'by', 'nosort', 'get', self.name + ':*->' + f, function(err, facetList) { var counts, len5, o, ref2, x; counts = _.countBy(facetList, function(p) { return p; }); ref2 = Object.keys(counts); for (o = 0, len5 = ref2.length; o < len5; o++) { x = ref2[o]; facetResults[f].push({ item: x, count: counts[x] }); } return resolve(); }); })); }); return Promise.all(facetsPromises).then(function() { return new Promise(function(resolve) { return self.redis.zrevrange(idKey, start, end, function(error, ids) { if (args.sortDirection === 'desc') { ids.reverse(); } self.redis.del(idKey, function() {}); return resolve({ ids: ids, totalResults: totalResults, facetResults: facetResults }); }); }); }); }); return matchedIdsPromise.then(function(resultObject) { var ids, promises; ids = resultObject.ids; if (args.sortBy === 'random') { if (args.limit) { ids = _.sample(ids, args.limit); } else { ids = _.shuffle(ids); } } promises = _.map(ids, function(id) { return self.find(id); }); return Promise.all(promises).then(function(resultItems) { var _resultObject; return _resultObject = { name: self.name, total: resultObject.totalResults, offset: start, facets: resultObject.facetResults, items: resultItems }; }); }); }); }, create: function(props, skipValidation) { return new Promise((function(_this) { return function(resolve, reject) { return sendAttributesForSaving.apply(_this, [props, skipValidation]).then(function(writtenObject) { if (writtenObject) { return resolve(_this.find(writtenObject.id)); } }, function(error) { if (error) { return reject(error); } }); }; })(this)); }, update: function(id, updateFields, skipValidation) { var callbackPromises, getOriginalObjPromise, multi, self, updateFieldsDiff; self = this; updateFieldsDiff = { id: id }; callbackPromises = []; multi = self.redis.multi(); getOriginalObjPromise = self.find(id).then(function(originalObj) { if (!originalObj) { throw new Error("Not Found"); } return originalObj; }); return getOriginalObjPromise.then(function(originalObj) { var attr, intersectingValues, j, len, multiPromise, namespace, newValue, obj, orderedSetName, originalIds, originalValue, ref, remove, removeValue, sortedSetName; ref = Object.keys(updateFields); for (j = 0, len = ref.length; j < len; j++) { attr = ref[j]; remove = false; if (attr.match(/^remove_/)) { remove = true; removeValue = updateFields[attr]; attr = attr.replace(/^remove_/, ''); } orderedSetName = self.name + '>' + attr; originalValue = originalObj[attr]; newValue = updateFields[attr]; if (newValue !== originalValue || _.includes([true, 'true', false, 'false'], newValue) || removeValue) { updateFieldsDiff[attr] = newValue; if (remove) { delete updateFieldsDiff[attr]; } obj = self.attributes[attr]; if (!obj) { return; } switch (obj.dataType) { case 'integer': sortedSetName = self.name + '>' + attr; multi.zrem(sortedSetName, id); break; case 'text': if (obj.searchable) { callbackPromises.push(removeIndexedSearchableString.apply(self, [attr, originalValue, id])); } break; case 'string': if (obj.sortable) { multi.zrem(orderedSetName, id); } if (obj.searchable) { callbackPromises.push(removeIndexedSearchableString.apply(self, [attr, originalValue, id])); } if (obj.identifiable) { multi.del(self.name + "#" + attr + ":" + originalValue); } break; case 'reference': namespace = obj.reverseReferenceAttribute || attr; if (obj.many) { if (remove) { multi.srem.apply(multi, [self.name + ":" + id + "#" + attr + ':' + obj.referenceModelName + 'Refs'].concat(slice.call(removeValue))); removeValue.forEach(function(vid) { return multi.srem(obj.referenceModelName + ":" + vid + "#" + namespace + ':' + self.name + 'Refs', id); }); } else { originalIds = _.map(originalValue, 'id'); intersectingValues = _.intersection(originalIds, newValue); if (!_.isEmpty(intersectingValues)) { updateFieldsDiff[attr] = intersectingValues; } } } else { if (remove) { multi.srem(obj.referenceModelName + ":" + originalValue + "#" + namespace + ':' + self.name + 'Refs', id); } } break; case 'boolean': multi.zrem(self.name + "#" + attr + ":" + originalValue, id); } } } multiPromise = new Promise(function(resolve, reject) { return multi.exec(function() { return sendAttributesForSaving.apply(self, [updateFieldsDiff, skipValidation]).then(function(writtenObj) { return resolve(self.find(writtenObj.id)); }, function(error) { return reject(error); }); }); }); return multiPromise.then(function(obj) { return Promise.all(callbackPromises).then(function() { return obj; }); }); }); } }; module.exports = redisObjectDataStore; }).call(this);