2014-01-26 14:46:25 -08:00
storage = require ' lib/storage '
2014-04-09 19:07:44 -07:00
deltasLib = require ' lib/deltas '
2014-04-10 13:09:44 -07:00
auth = require ' lib/auth '
2014-01-26 14:46:25 -08:00
2014-01-03 10:32:13 -08:00
class CocoModel extends Backbone . Model
idAttribute: " _id "
loaded: false
loading: false
2014-01-26 14:46:25 -08:00
saveBackups: false
2014-01-03 10:32:13 -08:00
@schema: null
initialize: ->
super ( )
2014-04-12 10:51:02 -07:00
@ constructor . schema ? = require " schemas/models/ #{ @ urlRoot [ 4 . . ] . replace ' . ' , ' _ ' } "
2014-01-03 10:32:13 -08:00
if not @ constructor . className
console . error ( " #{ @ } needs a className set. " )
@ markToRevert ( )
2014-04-12 10:51:02 -07:00
@ addSchemaDefaults ( )
2014-01-26 14:46:25 -08:00
@ once ' sync ' , @ onLoaded , @
@saveBackup = _ . debounce ( @ saveBackup , 500 )
2014-02-12 12:41:41 -08:00
2014-01-03 10:32:13 -08:00
type: ->
@ constructor . className
2014-04-13 14:48:36 -07:00
2014-04-11 21:11:52 -07:00
clone: (withChanges=true) ->
# Backbone does not support nested documents
clone = super ( )
clone . set ( $ . extend ( true , { } , if withChanges then @ attributes else @ _revertAttributes ) )
clone
2014-01-03 10:32:13 -08:00
2014-01-26 14:46:25 -08:00
onLoaded: ->
2014-01-03 10:32:13 -08:00
@loaded = true
@loading = false
2014-04-12 10:51:02 -07:00
@ markToRevert ( )
@ loadFromBackup ( )
2014-02-12 12:41:41 -08:00
2014-01-26 14:46:25 -08:00
set: ->
res = super ( arguments . . . )
2014-02-05 11:03:32 -08:00
@ saveBackup ( ) if @ saveBackups and @ loaded and @ hasLocalChanges ( )
2014-01-26 14:46:25 -08:00
res
2014-02-12 12:41:41 -08:00
2014-03-23 10:29:08 -07:00
loadFromBackup: ->
return unless @ saveBackups
existing = storage . load @ id
if existing
@ set ( existing , { silent : true } )
CocoModel . backedUp [ @ id ] = @
2014-01-26 14:46:25 -08:00
saveBackup: ->
storage . save ( @ id , @ attributes )
CocoModel . backedUp [ @ id ] = @
2014-02-12 12:41:41 -08:00
2014-01-26 14:46:25 -08:00
@backedUp = { }
2014-01-03 10:32:13 -08:00
schema: -> return @ constructor . schema
validate: ->
2014-04-12 14:05:56 +05:30
result = tv4 . validateMultiple ( @ attributes , @ constructor . schema ? or { } )
2014-01-03 10:32:13 -08:00
if result . errors ? . length
console . log @ , " got validate result with errors: " , result
return result . errors unless result . valid
save: (attrs, options) ->
2014-04-17 17:30:55 -07:00
@ set ' editPath ' , document . location . pathname
2014-01-03 10:32:13 -08:00
options ? = { }
success = options . success
options.success = (resp) =>
@ trigger " save:success " , @
success ( @ , resp ) if success
@ markToRevert ( )
2014-01-26 14:46:25 -08:00
@ clearBackup ( )
2014-01-03 10:32:13 -08:00
@ trigger " save " , @
return super attrs , options
fetch: ->
2014-03-11 22:01:38 -07:00
res = super ( arguments . . . )
2014-01-03 10:32:13 -08:00
@loading = true
2014-03-11 22:01:38 -07:00
res
2014-01-03 10:32:13 -08:00
markToRevert: ->
2014-03-23 10:00:16 -07:00
if @ type ( ) is ' ThangType '
@_revertAttributes = _ . clone @ attributes # No deep clones for these!
else
2014-03-22 02:21:23 +00:00
@_revertAttributes = $ . extend ( true , { } , @ attributes )
2014-01-03 10:32:13 -08:00
revert: ->
@ set ( @ _revertAttributes , { silent: true } ) if @ _revertAttributes
2014-01-26 14:46:25 -08:00
@ clearBackup ( )
2014-02-12 12:41:41 -08:00
2014-01-26 14:46:25 -08:00
clearBackup: ->
storage . remove @ id
2014-01-03 10:32:13 -08:00
hasLocalChanges: ->
not _ . isEqual @ attributes , @ _revertAttributes
cloneNewMinorVersion: ->
newData = $ . extend ( null , { } , @ attributes )
2014-04-11 22:33:09 -07:00
clone = new @ constructor ( newData )
clone
2014-01-03 10:32:13 -08:00
cloneNewMajorVersion: ->
clone = @ cloneNewMinorVersion ( )
clone . unset ( ' version ' )
clone
isPublished: ->
for permission in @ get ( ' permissions ' ) or [ ]
return true if permission . target is ' public ' and permission . access is ' read '
false
publish: ->
if @ isPublished ( ) then throw new Error ( " Can ' t publish what ' s already-published. Can ' t kill what ' s already dead. " )
@ set " permissions " , ( @ get ( " permissions " ) or [ ] ) . concat ( { access: ' read ' , target: ' public ' } )
addSchemaDefaults: ->
2014-04-12 10:51:02 -07:00
return if @ addedSchemaDefaults
2014-01-03 10:32:13 -08:00
@addedSchemaDefaults = true
2014-04-12 14:05:56 +05:30
for prop , defaultValue of @ constructor . schema . default or { }
2014-01-03 10:32:13 -08:00
continue if @ get ( prop ) ?
#console.log "setting", prop, "to", defaultValue, "from attributes.default"
@ set prop , defaultValue
2014-04-12 14:05:56 +05:30
for prop , sch of @ constructor . schema . properties or { }
2014-01-03 10:32:13 -08:00
continue if @ get ( prop ) ?
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
@ set prop , sch . default if sch . default ?
2014-03-23 10:29:08 -07:00
if @ loaded
@ markToRevert ( )
@ loadFromBackup ( )
2014-01-03 10:32:13 -08:00
2014-02-15 17:29:54 -08:00
getReferencedModels: (data, schema, path='/', shouldLoadProjection=null) ->
2014-01-03 10:32:13 -08:00
# returns unfetched model shells for every referenced doc in this model
# OPTIMIZE so that when loading models, it doesn't cause the site to stutter
data ? = @ attributes
2014-04-12 14:05:56 +05:30
schema ? = @ schema ( )
2014-01-03 10:32:13 -08:00
models = [ ]
if $ . isArray ( data ) and schema . items ?
for subData , i in data
2014-02-15 17:29:54 -08:00
models = models . concat ( @ getReferencedModels ( subData , schema . items , path + i + ' / ' , shouldLoadProjection ) )
2014-01-03 10:32:13 -08:00
if $ . isPlainObject ( data ) and schema . properties ?
for key , subData of data
continue unless schema . properties [ key ]
2014-02-15 17:29:54 -08:00
models = models . concat ( @ getReferencedModels ( subData , schema . properties [ key ] , path + key + ' / ' , shouldLoadProjection ) )
2014-01-03 10:32:13 -08:00
2014-02-15 17:29:54 -08:00
model = CocoModel . getReferencedModel data , schema , shouldLoadProjection
2014-01-03 10:32:13 -08:00
models . push model if model
return models
2014-02-15 17:29:54 -08:00
@getReferencedModel: (data, schema, shouldLoadProjection=null) ->
2014-01-03 10:32:13 -08:00
return null unless schema . links ?
linkObject = _ . find schema . links , rel: " db "
return null unless linkObject
2014-03-11 18:30:25 -07:00
return null if linkObject . href . match ( " thang.type " ) and not @ isObjectID ( data ) # Skip loading hardcoded Thang Types for now (TODO)
2014-01-03 10:32:13 -08:00
# not fully extensible, but we can worry about that later
link = linkObject . href
link = link . replace ( ' {(original)} ' , data . original )
link = link . replace ( ' {(majorVersion)} ' , ' ' + ( data . majorVersion ? 0 ) )
link = link . replace ( ' {($)} ' , data )
2014-02-15 17:29:54 -08:00
@ getOrMakeModelFromLink ( link , shouldLoadProjection )
2014-01-03 10:32:13 -08:00
2014-02-15 17:29:54 -08:00
@getOrMakeModelFromLink: (link, shouldLoadProjection=null) ->
2014-01-03 10:32:13 -08:00
makeUrlFunc = (url) -> -> url
modelUrl = link . split ( ' / ' ) [ 2 ]
modelModule = _ . string . classify ( modelUrl )
modulePath = " models/ #{ modelModule } "
window . loadedModels ? = { }
try
Model = require modulePath
window . loadedModels [ modulePath ] = Model
catch e
console . error ' could not load model from link path ' , link , ' using path ' , modulePath
return
model = new Model ( )
2014-02-15 17:29:54 -08:00
if shouldLoadProjection ? model
sep = if link . search ( /\?/ ) is - 1 then " ? " else " & "
link += sep + " project=true "
2014-01-03 10:32:13 -08:00
model.url = makeUrlFunc ( link )
return model
@isObjectID: (s) ->
2014-04-11 20:38:34 -07:00
s . length is 24 and s . match ( /[a-f0-9]/gi ) ? . length is 24
2014-01-03 10:32:13 -08:00
2014-03-03 20:41:35 +01:00
hasReadAccess: (actor) ->
# actor is a User object
2014-04-10 13:09:44 -07:00
actor ? = auth . me
return true if actor . isAdmin ( )
2014-03-03 20:41:35 +01:00
if @ get ( ' permissions ' ) ?
for permission in @ get ( ' permissions ' )
if permission . target is ' public ' or actor . get ( ' _id ' ) is permission . target
return true if permission . access in [ ' owner ' , ' read ' ]
return false
2014-03-03 21:13:02 +01:00
hasWriteAccess: (actor) ->
# actor is a User object
2014-03-03 20:41:35 +01:00
2014-04-10 13:09:44 -07:00
actor ? = auth . me
return true if actor . isAdmin ( )
2014-03-03 20:41:35 +01:00
if @ get ( ' permissions ' ) ?
for permission in @ get ( ' permissions ' )
if permission . target is ' public ' or actor . get ( ' _id ' ) is permission . target
return true if permission . access in [ ' owner ' , ' write ' ]
return false
2014-04-13 14:48:36 -07:00
2014-04-09 16:09:35 -07:00
getDelta: ->
2014-04-11 21:11:52 -07:00
differ = deltasLib . makeJSONDiffer ( )
differ . diff @ _revertAttributes , @ attributes
2014-04-13 14:48:36 -07:00
2014-04-11 21:11:52 -07:00
applyDelta: (delta) ->
newAttributes = $ . extend ( true , { } , @ attributes )
jsondiffpatch . patch newAttributes , delta
@ set newAttributes
2014-04-13 14:48:36 -07:00
2014-04-09 19:07:44 -07:00
getExpandedDelta: ->
delta = @ getDelta ( )
2014-04-12 15:16:42 +05:30
deltasLib . expandDelta ( delta , @ _revertAttributes , @ schema ( ) )
2014-04-13 14:48:36 -07:00
2014-04-15 15:09:36 -07:00
watch: (doWatch=true) ->
$ . ajax ( " #{ @ urlRoot } / #{ @ id } /watch " , { type : ' PUT ' , data : { on : doWatch } } )
2014-04-16 11:02:40 -07:00
@watching = -> doWatch
2014-04-15 15:09:36 -07:00
watching: ->
2014-04-16 11:02:40 -07:00
return me . id in ( @ get ( ' watchers ' ) or [ ] )
2014-03-03 20:41:35 +01:00
2014-01-03 10:32:13 -08:00
module.exports = CocoModel