spincycle
Version:
A reactive message router and object manager that lets clients subscribe to object property changes on the server
359 lines (332 loc) • 15.5 kB
text/coffeescript
defer = require('node-promise').defer
all = require('node-promise').allOrNone
uuid = require('node-uuid')
OMgr = require('./OStore')
DB = require('./DB')
error = require('./Error').error
ResolveModule = require('./ResolveModule')
resolver = new ResolveModule()
debug = process.env["DEBUG"]
class SuperModel
SuperModel.resolver = resolver
SuperModel.prototype.resolver = resolver
SuperModel.oncreatelisteners = []
SuperModel.onCreate = (cb)->
SuperModel.oncreatelisteners.push cb
constructor:( ={})->
#console.log '============================================ SuperModel constructor for '+ ?.id
#if debug then console.dir
= .id or uuid.v4()
=
missing = true
.model.forEach (mp) =>
if not [mp.name]
if mp.array == 'true'
[mp.name] = []
else if mp.hashtable == 'true'
[mp.name] = {}
else
#if debug then console.log 'instantiating empty record value for '+mp.name+' with '+mp.default
if Array.isArray(mp.default)
[mp.name] = []
else
[mp.name] = mp.default or ' '
if mp.name == 'createdAt' or mp.name == 'createdBy' then missing = false
if missing
.model.push({ name: 'createdAt', public: true, value: 'createdAt' })
.model.push({ name: 'modifiedAt', public: true, value: 'modifiedAt' })
.model.push({ name: 'createdBy', public: true, value: 'createdBy' })
#
= .type
q = defer()
OMgr.storeObject(@)
if ._rev
#if debug then console.log 'setting _rev to '+ ._rev+' for '+ .type+' '+
= ._rev
.then( () =>
if (not or == ' ') then = Date.now()
if (not or == ' ') then = Date.now()
if (not or == ' ') then = 'SYSTEM'
if not .id then SuperModel.oncreatelisteners.forEach (listener) => listener(@)
#console.log '============================================ SuperModel constructor after loadFromIds...'
#console.dir @
if
if debug then console.log 'calling PostCreate on '+ +' with deferred '+q
else
#console.log '-- done resolving constructor for '+ .type
q.resolve(@)
, error)
return q
getRecord: () =>
@._getRecord(@, .model, )
_getRecord: (me, model, record) ->
#console.log '_getRecord for '+me.type+' id '+me.id
rv = {}
rv._rev = if
model.forEach (v) =>
#if debug then console.log 'getRecord resolving value '+v.name
#if debug then console.dir v
k = v.name
if (v.value and v.value isnt 0) and v.type # direct object reference
#if debug then console.log 'getRecord accessing property '+k+' of object '+ +' -> '+me[k]
if me[k]
if v.storedirectly
rv[k] = me[k]._getRecord(me[k], me[k].constructor.model, me[k].record)
else
rv[k] = me[k].id
else
rv[k] = null
else if (v.value and v.value isnt 0) and not v.type
#if debug then console.log 'direct value '+v.value+' me[v.value] = '+(me[v.value])+' record[k] = '+(record[k])
rv[k] = me[v.value]
if not rv[k] and rv[k] isnt 0
if record and record[k] then rv[k] = record[k] else rv[k] = undefined
else if v.hashtable
#if debug then console.log 'hashtable '
varr = []
ha = me[v.name] or []
for hk, hv of ha
if v.storedirectly
varr.push hv._getRecord(hv, hv.constructor.model, hv.record)
else
#console.log '====================== hashtable prop id is '+hv.id+' typeof '+(typeof hv.id)
varr.push hv.id
rv[k] = varr
else if v.array
varr = []
marr = me[v.name] or []
marr.forEach (av) -> if av and av isnt null and av isnt 'null'
if v.storedirectly
varr.push av._getRecord(av, av.constructor.model, av.record)
else
#if debug then console.log '====================== array prop id is '+av.id+' typeof '+(typeof av.id)
if av.id then varr.push av.id
rv[k] = varr
else
if debug then console.log '**************** AAAUAGHH!!! property '+k+' was not resolved in SuperModel::_getRecord'
rv.id =
rv.type = @.constructor.type
#console.log 'record done for '+me.type
return rv
toClient: () =>
#console.log '---------------------------------------- toClient -----------------------------------------------'
#console.dir @
r =
#console.log 'record is'
#console.dir r
ra = @.constructor.model
rv = {}
for k,v of r
ra.forEach (el) =>
if el.name == k and k != 'record' and el.public
#console.log 'adding propety '+k
res =
#console.dir res
rv[k] = res
if not rv[k] and el.default then rv[k] = el.default
rv.id =
rv.type = @.constructor.type
return rv
serialize: (updatedObj) =>
q = defer()
if debug then console.log 'SuperModel.serialize called'
= true
OMgr.storeObject(@)
delete updatedObj.record if updatedObj
#OMgr.updateObj(updatedObj) if updatedObj
record =
delete record.record if record.record
if then record._rev =
#if debug then console.dir record
#if debug then console.log 'actual object is '
#if debug then console.dir @
#if debug then console.log 'toClient is '
#if debug then console.dir
DB.set @.constructor.type, record, (res) =>
if debug then console.log ' * serialized and persisted '+ +" id "+
= false
if res then q.resolve(@) else q.resolve()
return q
delete:()=>
DB.remove(@)
prettyPrint: (name, value) =>
rv = value
#if (name == 'createdAt' or name == 'modifiedAt') and (value and value isnt 0) then rv = new Date(parseInt(value)).toUTCString()
return rv
unPrettify: (record) =>
#TODO: Perhaps if createdBy is used, but it should actually be solved by the client instead (request name for user id referenced)
record
loadFromIds:(model) =>
#console.log '------------------------------------------------> loadfromIds called for '+@.constructor.type+' '+ +' '+model.length+' properties'
#console.dir @
alldone = defer()
allpromises = []
if(not model or model.length == 0)
#console.log ' ++++++++++++++++ NO model ++++++++++++++'
q = defer()
allpromises.push(q)
q.resolve()
else
model.forEach (robj) =>
((resolveobj) =>
#console.log 'SuperModel::loadFromIds for property '+resolveobj.name
r = defer()
allpromises.push(r)
if resolveobj.value
if resolveobj.type # direct object reference by id
#console.log 'direct reference! '+resolveobj.name+' type '+resolveobj.type+' id "'+ [resolveobj.value]+'" is a space == '+( [resolveobj.value] and ( [resolveobj.value] != ' '))
if resolveobj.storedirectly
else
if ( [resolveobj.value] and ( [resolveobj.value] != ' '))
#console.log '-------------- resolving direct reference'
else
#console.log '+++++++++++++++ ugh'
#console.dir
@[resolveobj.name] = null
r.resolve(@[resolveobj.name])
else
#console.log 'supermodel loadfromids scalar'
#console.dir resolveobj
if [resolveobj.value]
@[resolveobj.name] = [resolveobj.value] or resolveobj.default # scalar
r.resolve(@[resolveobj.name])
else
#console.log 'testing for arrays. current value of '+resolveobj.name+' is '+@[resolveobj.name]+' record is "'+ [resolveobj.name]+'"'
if resolveobj.array == true
if (not @[resolveobj.name]) or (Array.isArray( [resolveobj.name]) and [resolveobj.name].length > 0)
#console.log 'resolveobjids -- setting empty array for '+resolveobj.name+' since either the property is currently undefined or the array records contain at least one reference which will be added on resolving'
@[resolveobj.name] = []
if ((resolveobj.hashtable == true) and (not @[resolveobj.name])) then @[resolveobj.name] = {}
#if resolveobj.name == 'foos' then console.log 'foos is now '+@[resolveobj.name]
#if resolveobj.name == 'foos' then console.dir @[resolveobj.name]
ids = [resolveobj.ids]
if not ids or typeof ids == 'undefined' or ids == 'undefined'
ids = []
#console.log '============================== null resolveobj.ids for '+resolveobj.type+' ['+resolveobj.name+']'
r.resolve()
else # array or hashtable by array of ids
if typeof ids is 'string'
ids = [ids]
if Array.isArray(ids)
ids = ids.filter (ii) -> ii and ii isnt null and ii isnt "null" and ii isnt "undefined"
else
temp = {}
for x,y of ids
if y and y isnt null and y isnt "null" and y isnt "undefined" then temp[x] = y
ids = temp
count = ids.length
#console.log 'ids.length -> '+ids.length
if (not count and count != 0)
#console.log 'no ids for '+resolveobj.name+' so resolving undefined'
r.resolve(undefined)
else if count == 0
#console.log 'resolving [] for '+resolveobj.name+' since empty array in record'
@[resolveobj.name] = []
r.resolve([])
else
ids.forEach (_id) =>
((id) =>
--count
if resolveobj.storedirectly
#console.log '** storedirectly creating array or hash object immediately..'
else
#console.log 'SuperModel loadFromIds trying to get '+resolveobj.name+' with id "'+id+'"'
if (!id or id == ' ') and @[resolveobj.name]
#console.log 'resolving existing array for '+resolveobj.name+' since this was empty'
#console.dir @[resolveobj.name]
r.resolve(@[resolveobj.name])
else
#console.log 'calling resolveObj for id '+id
)(_id)
#console.log 'resolveobjids '+resolveobj.name+' ('+(typeof ids)+') ids length are.. '+ids.length
#console.dir ids
#if debug then console.log '------- property '+resolveobj.name+' now set to '+@[resolveobj.name]
)(robj)
all(allpromises, error).then( (results) =>
#console.log '<------------------------------------------------ loadfromIds done for '+@.constructor.type+' '+ +' '+model.length+' properties'
#console.dir results
alldone.resolve(results)
,error)
return alldone
resolveObj: (resolveobj, id, r, count) =>
if typeof id != 'string'
if id.id
id = id.id
else
console.log '%%%%%%%%%%%%%%%%%%% ERROR: Supermodel.resolveObj given a non-string as id!! resolveObj was:'
console.dir resolveobj
console.log 'id was:'
console.dir id
process.exit(-1)
#console.log '************************************* resolveObj called in '+ +' for id '+id+' typeof '+(typeof id)
#console.dir id
OMgr.getObject(id, resolveobj.type).then( (oo) =>
if oo
#console.log 'SuperModel found existing instance of '+resolveobj.name+' type '+resolveobj.type+' in OStore'
if count == 0
#console.log 'SuperModel resolving '+resolveobj.name+' type '+resolveobj.type+' immediately'
#console.dir oo
r.resolve(oo)
else
if debug then console.log 'SuperModel did not find obj '+resolveobj.name+' ['+id+'] of type '+resolveobj.type+' in OStore. Getting from DB. typeof of id prop is '+(typeof id)
#console.dir resolveobj
DB.get(resolveobj.type, [id]).then( (records) =>
record = undefined
if records and records[0] then record = records[0]
#console.log 'supermodel resolveObj got back from DB.get '+record
if not record
#console.log 'SuperModel::loadFromIds got back null record from DB for type '+resolveobj.type+' and id '+id
#console.log 'current content is'
#console.dir @[resolveobj.name]
if count == 0 then r.resolve(@[resolveobj.name])
else
#console.log '** resolveObj no obj found and no record for id '+id+' type '+resolveobj.type
if not id or not resolveobj.type
r.resolve(null)
else
#console.log 'calling createObjectFromRecord for '+id+' type '+resolveobj.type
, error)
, error)
createObjectFromRecord: (r, resolveobj, count, record)=>
#if debug then console.log '################################## supermodel createObjectFromRecord called'
#if debug then console.dir resolveobj
#if debug then console.log 'record.....'
#if debug then console.dir record
if (Array.isArray(record))
throw(new Error('got array instead of record in supermodel createObjectFromRecord !!!'))
if record and record.type
SuperModel.resolver.createObjectFrom(record).then( (obj) =>
if not obj
#console.log ' Hmm. Missing object reference. Sad Face.'
#console.dir record
if count == 0 then r.resolve(null)
else
#console.log 'object '+resolveobj.name+' type '+resolveobj.type+' created: '+obj.id
if count == 0
#console.log '---- count zero'
r.resolve(obj)
, error)
else
r.resolve()
insertObj: (ro, o) =>
OMgr.storeObject(o)
if ro.array == true
#if debug then console.log 'inserting obj '+ro.type+' as array'
@[ro.name].push(o)
else if ro.hashtable == true
#if debug then console.log 'inserting obj '+ro.type+' as hashtable'
if ro.keyproperty then property = ro.keyproperty else property = 'name'
@[ro.name][o[property]] = o
else
#if debug then console.log 'inserting obj '+ro.type+' as direct reference'
@[ro.name] = o
OMgr.storeObject(o)
module.exports = SuperModel;