oomph
Version:
Object Oriented javascript models for the client and the server
658 lines (623 loc) • 29.3 kB
text/coffeescript
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 = ->
d = new Date()
s = (+d).toString(36)
s + _utilities.randomString(numberOfExtraCharactersOnId)
idToSeconds = (id) ->
parseInt(id.slice(0, -numberOfExtraCharactersOnId), 36)
idToCreatedAtDate = (id) ->
new Date idToSeconds(id)
sortAscendingNumbersFn = (a, b) -> b - a
sortAlphabeticallyFn = (a, b) -> a < b
generateUniqueId = ->
self = this
uniqueId = false
condition = -> uniqueId == false
newIdPromise = _utilities.promiseWhile condition, ->
new Promise (resolve) ->
id = generateId()
self.redis.lrange self.name + '#uniqueQueuedIds', 0, -1, (error, idsArray) ->
if _.includes(idsArray, id)
id = generateId()
resolve()
else
self.redis.rpush self.name + '#uniqueQueuedIds', id, (error, response) ->
uniqueId = true
resolve(id)
createObjectFromHash = (hash,modelPrototype) ->
obj = {}
Object.create(modelPrototype) if modelPrototype
return false if !hash
obj.createdAt = idToCreatedAtDate(hash.id) if hash.id
for key, value of hash
plainKey = key.replace /\[\w\]$/, ''
propertyCastType = key.match /\[(\w)\]$/
if propertyCastType
if propertyCastType[1] == 'b' # cast as boolean
obj[plainKey] = (value == 'true')
else if propertyCastType[1] == 'i' # cast as integer
obj[plainKey] = parseInt(value)
else
obj[key] = value
obj
validate = (validationObj, attrName, attrValue) ->
validations = []
for validationType in Object.keys(validationObj)
validationSetting = validationObj[validationType]
if validationType != 'presence' and !attrValue
continue
switch validationType
when 'presence'
if _utilities.isBlank(attrValue)
validations.push new ValidationError attrName + " must be present", attribute: attrName, expected: validationSetting
when 'equalTo'
unless attrValue == validationSetting
validations.push new ValidationError attrName + " should equal " + validationSetting, attribute: attrName, actual: attrValue, expected: validationSetting
when 'lessThan'
unless attrValue < validationSetting
validations.push new ValidationError attrName + " should be less than " + validationSetting, attribute: attrName, actual: attrValue, expected: validationSetting
when 'lessThanOrEqualTo'
unless attrValue <= validationSetting
validations.push new ValidationError attrName + " should be less than or equal to " + validationSetting, attribute: attrName, actual: attrValue, expected: validationSetting
when 'greaterThanOrEqualTo'
unless attrValue >= validationSetting
validations.push new ValidationError attrName + " should be greater than or equal to " + validationSetting, attribute: attrName, actual: attrValue, expected: validationSetting
when 'greaterThan'
unless attrValue > validationSetting
validations.push new ValidationError attrName + " should be greater than " + validationSetting, attribute: attrName, actual: attrValue, expected: validationSetting
when 'inclusionIn'
unless _.includes(validationSetting, attrValue)
validations.push new ValidationError attrName + " must be one of the accepted values", attribute: attrName, actual: attrValue, expected: validationSetting
when 'exclusionIn'
if _.includes(validationSetting, attrValue)
validations.push new ValidationError attrName + " must not be one of the forbidden values", attribute: attrName, actual: attrValue, expected: validationSetting
when 'uniqueness'
validationPromise = new Promise (resolve, reject) =>
@redis.get @name + '#' + attrName + ':' + attrValue, (error, obj) ->
if (error || obj)
resolve new ValidationError attrName + " should be a unique value", attribute: attrName, actual: attrValue, expected: validationSetting
else
resolve()
validations.push validationPromise
when 'length'
if validationSetting.hasOwnProperty('is')
unless 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')
unless 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')
unless 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
when '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')
unless 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
Promise.all(validations)
performValidations = (dataFields) ->
if _.isEmpty(dataFields)
throw new Error "No valid fields given"
returnedValidations = _.map @attributes, (attrObj, attrName) =>
if attrObj.validates
attrValue = dataFields[attrName]
return validate.apply(this, [attrObj.validates, attrName, attrValue])
Promise.all(returnedValidations).then (validationArray) ->
errors = _(validationArray).flattenDeep().compact().value()
throw errors if not _.isEmpty(errors)
indexSortedSet = (setKey, attr) ->
listKey = setKey + 'TempList'
setTmpKey = setKey + 'TempList'
sortPromise = new Promise (resolve, reject) =>
@redis.sort setKey, 'by', @name + ':*->' + attr, 'alpha', 'store', listKey, (error, newLength) ->
resolve(newLength)
addToTmpSetPromise = sortPromise.then (listLength) =>
multi = @redis.multi()
new Promise (resolve, reject) =>
_.times listLength, ->
multi.lpop listKey
multi.exec (error, ids) =>
addToSet = _.map ids, (id, i) =>
new Promise (r) =>
@redis.zadd setTmpKey, listLength - i, id, (res) ->
r()
resolve Promise.all(addToSet)
addToTmpSetPromise.then =>
new Promise (resolve) =>
@redis.rename setTmpKey, setKey, (res) =>
resolve()
indexSearchableString = (attr, words, id) ->
indexPromises = []
for word in words.split /\s/
word = word.toLowerCase()
wordSegment = ''
for char in word
wordSegment += char
wordSegmentKey = @name + '#' + attr + '/' + wordSegment
indexPromiseFn = (wordSegmentKey, id) =>
new Promise (resolve) =>
@redis.zadd wordSegmentKey, 1, id, (res) ->
resolve()
indexPromises.push indexPromiseFn(wordSegmentKey, id)
Promise.all(indexPromises)
removeIndexedSearchableString = (attr, words, id) ->
indexPromises = []
for word in words.split /\s/
word = word.toLowerCase()
wordSegment = ''
for char in word
wordSegment += char
wordSegmentKey = @name + '#' + attr + '/' + wordSegment
indexPromiseFn = (wordSegmentKey, id) =>
new Promise (resolve) =>
@redis.zrem wordSegmentKey, id, (res) ->
resolve()
indexPromises.push indexPromiseFn(wordSegmentKey, id)
Promise.all(indexPromises)
writeAttributes = (props) ->
self = this
newObjectFlag = false
idPromise = new Promise (resolve) ->
if !props.id
newObjectFlag = true
generateUniqueId.apply(self).then (id) ->
resolve id
else
resolve props.id
writePromise = idPromise.then (id) ->
props.id = id
storableProps = _.clone props
new Promise (resolve) ->
for attr, obj of self.attributes
switch obj.dataType
when 'integer'
if storableProps[attr]
storableProps[attr + '[i]'] = storableProps[attr]
delete storableProps[attr]
when 'boolean'
if storableProps[attr]
storableProps[attr + '[b]'] = storableProps[attr]
delete storableProps[attr]
when 'reference'
if obj.many
delete storableProps[attr]
storableProps[attr] = true if newObjectFlag
self.redis.hmset self.name + ":" + props.id, storableProps, (err, res) ->
resolve(storableProps)
indexPromise = writePromise.then (storedProps) ->
indexingPromises = []
multi = self.redis.multi()
indexPromiseFn = (sortedSetName, attributeName) ->
largestSortedSetSize = 9007199254740992 # make sure new elements are added at the end of the set
new Promise (resolve) ->
self.redis.zadd sortedSetName, largestSortedSetSize, props.id, (error, res) ->
indexSortedSet.apply(self, [sortedSetName, attributeName]).then ->
resolve()
sortedSetName = self.name + ">id"
indexingPromises.push indexPromiseFn(sortedSetName, "id")
for attr, obj of self.attributes
continue if props[attr] == undefined #props[attr] can be false for boolean dataType
value = props[attr]
switch obj.dataType
when 'integer'
sortedSetName = self.name + ">" + attr
multi.zadd sortedSetName, parseInt(value), props.id #sorted set
when 'string'
if obj.sortable
sortedSetName = self.name + ">" + attr
indexingPromises.push indexPromiseFn(sortedSetName, attr)
if obj.identifiable
multi.set self.name + "#" + attr + ":" + value, props.id #string
if obj.searchable
indexingPromises.push indexSearchableString.apply(self, [attr, value, props.id])
when 'text'
if obj.searchable
indexingPromises.push indexSearchableString.apply(self, [attr, value, props.id])
when 'boolean'
if _.includes([true, 'true', false, 'false'], value)
multi.zadd self.name + "#" + attr + ":" + value, 1, props.id #set
when 'reference'
namespace = obj.reverseReferenceAttribute || attr
if obj.many
multipleValues = _.compact(value.split(","))
multi.sadd self.name + ":" + props.id + "#" + attr + ':' + obj.referenceModelName + 'Refs', multipleValues...
multipleValues.forEach (vid) ->
multi.sadd obj.referenceModelName + ":" + vid + "#" + namespace + ':' + self.name + 'Refs', props.id
else
multi.sadd obj.referenceModelName + ":" + value + "#" + namespace + ':' + self.name + 'Refs', props.id
else
if obj['dataType'] != null
reject new Error "Unrecognised dataType " + obj.dataType
new Promise (resolve) ->
multi.exec ->
resolve Promise.all(indexingPromises)
indexPromise.then ->
return props
clearUniqueQueuedIds = ->
@redis.del @name + '#uniqueQueuedIds'
processWriteQueue = ->
self = this
hasQueue = true
condition = -> hasQueue
writeReturnObject = {}
processPromise = _utilities.promiseWhile condition, ->
writePromise = new Promise (resolve, reject) ->
self.redis.rpop self.name + "#TmpQueue", (error, tmpId) ->
if tmpId
self.redis.hgetall self.name + "#TmpQueueObj:" + tmpId, (err, props) ->
self.redis.del self.name + "#TmpQueueObj:" + tmpId
if props
writeAttributes.apply(self, [props]).then (writtenObject) ->
writeReturnObject[tmpId] = writtenObject
resolve()
else
reject new Error "No properties in Queued Object " + self.name + "#TmpQueueObj:" + tmpId
else
clearUniqueQueuedIds.apply(self)
hasQueue = false
resolve()
processPromise.then ->
_.each writeReturnObject, (obj, tmpId) ->
publishSubscribe.broadcast.apply(self, ["attributes_written_" + tmpId, obj])
return writeObjectArray
addToWriteQueue = (props) ->
self = this
tmpId = "TmpId" + generateId() + _utilities.randomString(12)
p = new Promise (resolve) ->
self.redis.hmset self.name + "#TmpQueueObj:" + tmpId, props, (err, res) =>
self.redis.lpush self.name + "#TmpQueue", tmpId, (error, newListLength) =>
resolve(tmpId)
p.then (tmpId) ->
clearTimeout self._ORMWriteQueueTimeout
self._ORMWriteQueueTimeout = setTimeout ->
return processWriteQueue.apply(self)
, 100
new Promise (resolve) ->
resolveFn = (obj) ->
publishSubscribe.removeAllListenersOn.apply self, ["attributes_written_" + tmpId]
resolve(obj)
publishSubscribe.listen.apply self, ["attributes_written_" + tmpId, resolveFn]
sendAttributesForSaving = (dataFields, skipValidation) ->
if skipValidation
validationPromise = new Promise (resolve) ->
resolve(true)
props = dataFields
else
attrs = _.keys(@attributes)
attrs.push "id"
sanitisedDataFields = _(dataFields).omit(_.isNull).omit(_.isUndefined).pick(attrs).value()
props = sanitisedDataFields
validationPromise = performValidations.apply(this, [props])
new Promise (resolve, reject) =>
reject new Error "Properties are empty" if _.isEmpty props
validationPromise.then =>
resolve addToWriteQueue.apply(this, [props])
, (validationErrors) ->
reject validationErrors
createIntegerSubset = (integerSortedSetName, tempIntegerKey, minValue, maxValue) ->
self = this
new Promise (resolve) ->
self.redis.zrangebyscore integerSortedSetName, minValue, maxValue, (error, resultList) ->
multi = self.redis.multi()
for result in resultList
multi.zadd tempIntegerKey, 0, result
multi.expire tempIntegerKey, 1 #key will expire in 1 second. Key not explicitly deleted
multi.exec ->
resolve()
findKeywordsInAnyFields = (fields, keywords, weightOptions) ->
unionkeyNames = []
unionKeyPromises = _.map keywords, (keyword) =>
keyNames = []
for field in fields
weight = (if weightOptions[field] and weightOptions[field].weight then weightOptions[field].weight else 1)
keyNames.push name: @name + "#" + field + "/" + keyword, weight: weight
unionKey = 'keywordUnionSet:' + _utilities.randomString(5)
unionKeyPromise = new Promise (resolve) =>
@redis.zunionstore unionKey, keyNames.length, _.map(keyNames, 'name')..., 'weights', _.map(keyNames, 'weight')..., ->
resolve unionKey
unionKeyPromise.then (unionKey) ->
unionkeyNames.push unionKey
Promise.all(unionKeyPromises).then ->
return unionkeyNames
redisObjectDataStore =
moduleName: "redisObjectDataStore"
moduleInitialise: ->
@redis = redis.createClient()
all: (args) ->
allArgs = _.pick(args, ['sortBy', 'sortDirection', 'limit', 'offset'])
@where(allArgs)
# FIXME: In need of refactor - lots of code smell
find: (id) ->
self = this
referencePromises = []
getHash = new Promise (resolve, reject) ->
self.redis.hgetall self.name + ":" + id, (error, hash) ->
if !hash
reject new Error "Not Found"
else
resolve(hash)
modifyHashPromise = getHash.then (hash) ->
for propertyName in Object.keys(self.attributes)
propertyValue = hash[propertyName]
attrSettings = self.attributes[propertyName]
continue if _.isUndefined(propertyValue)
if attrSettings.dataType == 'reference'
if attrSettings.many
getReferenceIdsFn = (propertyName, referenceModelName) ->
new Promise (resolve, reject) ->
referenceKey = self.name + ':' + id + '#' + propertyName + ':' + referenceModelName + 'Refs'
self.redis.smembers referenceKey, (err, ids) ->
hashObj = {propertyName, referenceModelName}
resolve {ids, hashObj}
referencePromise = getReferenceIdsFn(propertyName, attrSettings.referenceModelName).then (obj) ->
getObjects = _.map obj.ids, (id) ->
new Promise (resolve, reject) ->
self.redis.hgetall obj.hashObj.referenceModelName + ':' + id, (err, hash) ->
resolve createObjectFromHash hash
Promise.all(getObjects).then (arr) ->
obj.hashObj.referenceValue = arr
obj.hashObj
referencePromises.push referencePromise
else
hashPromiseFn = (propertyName, propertyValue, referenceModelName) ->
new Promise (resolve, reject) ->
self.redis.hgetall referenceModelName + ':' + propertyValue, (err, hash) ->
hashObj = {propertyName, propertyValue, referenceModelName}
hashObj.referenceValue = createObjectFromHash hash
resolve hashObj
referencePromises.push hashPromiseFn(propertyName, propertyValue, attrSettings.referenceModelName)
delete hash[propertyName]
hash
modifyHashPromise.then (hash) ->
Promise.all(referencePromises).then (referenceObjects) ->
_.each referenceObjects, (refObj) ->
hash[refObj.propertyName] = refObj.referenceValue
createObjectFromHash(hash, self.prototype)
findBy: (option) ->
p = new Promise (resolve, reject) =>
optionName = Object.keys(option)[0]
condition = option[optionName]
if optionName == 'id'
resolve condition
else
if @attributes[optionName].dataType == 'string' and @attributes[optionName].identifiable
stringName = @name + "#" + optionName + ":" + condition
@redis.get stringName, (err, res) ->
resolve res
else
reject( throw new Error "Not an identifier" )
p.then (res) =>
@find(res)
where: (args) ->
self = this
args ||= {}
args.sortBy ||= (if args.includes then 'relevance' else 'id')
args.sortDirection ||= 'asc'
args.sortDirection.toLowerCase()
args.limit ||= null
args.offset ||= null
if args.sortBy == 'random'
start = 0
end = -1
else
start = +args.offset
end = if args.limit > 0 then (args.limit - 1) + args.offset else -1
if args.sortDirection == 'desc'
end = if args.offset > 0 then -(args.offset + 1) else -1
start = if args.limit > 0 then end - (args.limit - 1) else 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 (r) -> r()
if args.includes
if args.includes.modifiedWeights
for modifyObj in args.includes.modifiedWeights
modifyObjAttrs = if _.isArray(modifyObj.attributes) then modifyObj.attributes else [modifyObj.attributes]
for attr in modifyObjAttrs
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, (s) -> s == '' )
if args.includes.inAnyOf
keywordSearchPromise = findKeywordsInAnyFields.apply(self, [fields, keywords, weightOptions]).then (keyNames) ->
_.each keyNames, (key) ->
sortedSetKeys.push name: key
else if args.includes.inAllOf or args.includes.in
for field in fields
weight = (if weightOptions[field] and weightOptions[field].weight then weightOptions[field].weight else 1)
for keyword in keywords
sortedSetKeys.push name: self.name + "#" + field + "/" + keyword, weight: weight
whereConditionPromises = []
for option in Object.keys(args)
optionValue = args[option]
if not @attributes[option]
continue
switch @attributes[option].dataType
when 'integer' #add less than and greater than functionality
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 (resolve) ->
createIntegerSubset.apply(self, [integerSortedSetName, tempIntegerKey, minValue, maxValue]).then ->
resolve()
sortedSetKeys.push name: tempIntegerKey
sortedSetKeys.push name: integerSortedSetName
when 'boolean'
sortedSetKeys.push name: self.name + "#" + option + ":" + optionValue
when 'reference'
referenceModelName = @attributes[option].referenceModelName
if referenceModelName
namespace = @attributes[option].reverseReferenceAttribute || option
if @attributes[option].many
if optionValue.includesAllOf
_.each optionValue.includesAllOf, (id) ->
sortedSetKeys.push name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs'
if optionValue.includesAnyOf
_.each optionValue.includesAnyOf, (id) ->
unionSortedSetKeys.push name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs'
else
if optionValue.anyOf
_.each optionValue.anyOf, (id) ->
unionSortedSetKeys.push name: referenceModelName + ':' + id + '#' + namespace + ':' + self.name + 'Refs'
else
sortedSetKeys.push name: referenceModelName + ':' + optionValue + '#' + namespace + ':' + self.name + 'Refs'
prepareWhereConditionPromise = Promise.all(whereConditionPromises).then ->
if _.isEmpty(unionSortedSetKeys)
keywordSearchPromise
else
unionPromise = new Promise (resolve) ->
unionSortedSetKeyNames = _.map(unionSortedSetKeys, 'name')
unionKey = 'temporaryUnionSet:' + _utilities.randomString(24)
self.redis.zunionstore unionKey, unionSortedSetKeys.length, unionSortedSetKeyNames..., (err,numberofresults) ->
self.redis.expire unionKey, 5 # FIXME: this shoud be cached
sortedSetKeys.push name: unionKey
resolve()
unionPromise.then ->
keywordSearchPromise
prepareWhereConditionPromise.then () ->
idsKeyPromise = new Promise (resolve) ->
intersectKey = 'temporaryIntersectSet:' + _utilities.randomString(24)
sortedSetKeyNames = _.map(sortedSetKeys, 'name')
self.redis.zinterstore intersectKey, sortedSetKeys.length, sortedSetKeyNames..., (err,numberOfResults) ->
self.redis.expire intersectKey, 5 # FIXME: this shoud be cached
resolve({intersectKey,numberOfResults})
matchedIdsPromise = idsKeyPromise.then (resultObj) ->
idKey = resultObj.intersectKey
totalResults = resultObj.numberOfResults
facetResults = {}
facetsPromises = []
_.each args.facets, (f) ->
facetResults[f] = []
facetsPromises.push new Promise (resolve) ->
self.redis.sort idKey, 'by', 'nosort', 'get', self.name + ':*->' + f, (err, facetList) ->
counts = _.countBy facetList, (p) -> p
for x in Object.keys(counts)
facetResults[f].push item: x, count: counts[x]
resolve()
Promise.all(facetsPromises).then ->
new Promise (resolve) ->
self.redis.zrevrange idKey, start, end, (error, ids) ->
ids.reverse() if args.sortDirection == 'desc'
self.redis.del idKey, ->
resolve {ids, totalResults, facetResults}
matchedIdsPromise.then (resultObject) ->
ids = resultObject.ids
if args.sortBy == 'random'
if args.limit
ids = _.sample ids, args.limit
else
ids = _.shuffle ids
promises = _.map ids, (id) ->
self.find(id)
Promise.all(promises).then (resultItems) ->
_resultObject =
name: self.name
total: resultObject.totalResults
offset: start
facets: resultObject.facetResults
items: resultItems
create: (props, skipValidation) ->
new Promise (resolve, reject) =>
sendAttributesForSaving.apply(this, [props, skipValidation]).then (writtenObject) =>
resolve @find(writtenObject.id) if writtenObject
, (error) ->
reject error if error
update: (id, updateFields, skipValidation) ->
self = this
updateFieldsDiff = { id: id } #need to send existing id to stop new id being generated
callbackPromises = []
multi = self.redis.multi()
getOriginalObjPromise = self.find(id).then (originalObj) ->
if !originalObj
throw new Error "Not Found"
return originalObj
getOriginalObjPromise.then (originalObj) ->
for attr in Object.keys(updateFields)
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 there is an actual change or it's a boolean
if newValue != originalValue or _.includes([true, 'true', false, 'false'], newValue) or removeValue
updateFieldsDiff[attr] = newValue
delete updateFieldsDiff[attr] if remove
obj = self.attributes[attr]
return if !obj
switch obj.dataType
when 'integer'
sortedSetName = self.name + '>' + attr
multi.zrem sortedSetName, id
when 'text'
if obj.searchable
callbackPromises.push removeIndexedSearchableString.apply(self, [attr, originalValue, id])
when '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
when 'reference'
namespace = obj.reverseReferenceAttribute || attr
if obj.many
if remove
multi.srem self.name + ":" + id + "#" + attr + ':' + obj.referenceModelName + 'Refs', removeValue...
removeValue.forEach (vid) ->
multi.srem obj.referenceModelName + ":" + vid + "#" + namespace + ':' + self.name + 'Refs', id
else
originalIds = _.map(originalValue, 'id')
intersectingValues = _.intersection(originalIds, newValue)
updateFieldsDiff[attr] = intersectingValues if !_.isEmpty(intersectingValues)
else
if remove
multi.srem obj.referenceModelName + ":" + originalValue + "#" + namespace + ':' + self.name + 'Refs', id
when 'boolean'
multi.zrem self.name + "#" + attr + ":" + originalValue, id
multiPromise = new Promise (resolve, reject) ->
multi.exec ->
sendAttributesForSaving.apply(self, [updateFieldsDiff, skipValidation]).then (writtenObj) ->
resolve self.find(writtenObj.id)
, (error) ->
reject error
multiPromise.then (obj) ->
Promise.all(callbackPromises).then ->
return obj
module.exports = redisObjectDataStore