base-domain
Version:
simple module to help build Domain-Driven Design
542 lines (384 loc) • 12.7 kB
text/coffeescript
'use strict'
TypeInfo = require './type-info'
Base = require './base'
ModelProps = require './model-props'
Util = require '../util'
###*
Base model class of DDD pattern.
BaseModel
Base
base-domain
###
class BaseModel extends Base
: false
###*
Flag of the model's immutablity
{Boolean} isImmutable
###
: false
###*
key-value pair representing typeName - type
use for definition of for each extender
TYPES
Object
###
: TypeInfo.TYPES
###*
key-value pair representing property's name - type of the model
firstName : .STRING
lastName : .STRING
age : .NUMBER
registeredAt : .DATE
team : .MODEL 'team'
hobbies : .MODEL 'hobby-list'
info : .ANY
see type-info.coffee for full options.
properties
Object
###
: {}
###*
extend of Parent class
class Parent extends BaseModel
:
prop1: .STRING
class ChildModel extends ParentModel
:
prop2: .NUMBER
ChildModel.properties # prop1 and prop2
withParentProps
{Object}
###
: (properties = {}) ->
properties[k] ?= v for k, v of # === parent's properties
return properties
###*
enum
{Object([key: String => Number])}
###
: (prop) ->
# TODO Object.assign()
?[prop]?.numsByValue
###*
enum
{Object}
###
enum: (prop) ->
().getEnumDic(prop)
###*
getModelProps
{ModelProps}
###
getModelProps: ->
if ?
.getModelProps(.getName())
else
new ModelProps(.getName(), .properties, null)
###*
{any} obj
{RootInterface} root
###
constructor: (obj, root) ->
super(root)
obj if obj
###*
set value to prop
{BaseModel} this
###
set: (prop, value) ->
if typeof prop is 'object'
(k, v) for k, v of prop
return @
@[prop] = value
modelProps = ()
# set entity prop
if modelProps.isEntity(prop)
subIdProp = modelProps.getIdPropByEntityProp(prop)
@[subIdProp] = value?.id
# set submodel id prop
else if modelProps.isId(prop) and value?
@[prop] = value
submodelProp = modelProps.getEntityPropByIdProp(prop)
# if new submodel id is set and old one exists, delete old one
if @[submodelProp]? and @[prop] isnt @[submodelProp].id
@[submodelProp] = undefined
# set enum
else if modelProps.isEnum(prop)
(prop, value)
return @
###*
set value to prop and create a new model
$set
{BaseModel} this
###
$set: (prop, value) ->
if typeof prop is 'object'
return (prop)
props = {}
props[prop] = value
return (props)
###*
set enum value
setEnum
{String} prop
{String|Number} value
###
setEnum: (prop, value) ->
return if not value?
modelProps = ()
enums = modelProps.getEnumDic(prop)
if typeof value is 'string' and enums[value]?
return @[prop] = enums[value]
else if typeof value is 'number' and modelProps.getEnumValues(prop)[value]?
return @[prop] = value
console.error("""
base-domain: Invalid value is passed to ENUM prop "#{prop}" in model "#{modelProps.modelName}".
Value: "#{value}"
The property was not set.
""")
###*
unset property
unset
{String} prop property name
{BaseModel} this
###
unset: (prop) ->
@[prop] = undefined
modelProps = ()
if modelProps.isEntity(prop)
subIdProp = modelProps.getIdPropByEntityProp(prop)
@[subIdProp] = undefined
return @
###*
unset property and create a new model
$unset
{String} prop property name
{BaseModel} this
###
$unset: (prop) ->
props = {}
props[prop] = null
modelProps = ()
if modelProps.isEntity(prop)
subIdProp = modelProps.getIdPropByEntityProp(prop)
props[subIdProp] = null
return (props)
###*
inherit value of anotherModel
inherit
{BaseModel} anotherModel
{BaseModel} this
###
inherit: (anotherModel) ->
(k, v) for own k, v of anotherModel when v?
return @
###*
create plain object without relational entities
descendants of Entity are removed, but not descendants of BaseModel
descendants of Entity in descendants of BaseModel are removed ( = recursive)
toPlainObject
{Object} plainObject
###
toPlainObject: ->
plainObject = {}
modelProps = ()
for own prop, value of @
continue if modelProps.isEntity(prop) or modelProps.isOmitted(prop)
if typeof value?.toPlainObject is 'function'
plainObject[prop] = value.toPlainObject()
else
plainObject[prop] = value
return plainObject
###*
check equality
equals
{BaseModel} model
{Boolean}
###
equals: (model) ->
model? and is model.constructor
###*
clone the model as a plain object
plainClone
{Object}
###
plainClone: ->
plainObject = {}
modelProps = ()
for own prop, value of @
if modelProps.isModel and value instanceof BaseModel
plainObject[prop] = value.plainClone()
else
plainObject[prop] = Util.clone value
return plainObject
###*
create clone
clone
{BaseModel}
###
clone: ->
plainObject = ()
modelProps = ()
return .createModel modelProps.modelName, plainObject
###*
shallow copy the model with props
copyWith
{BaseModel}
###
copyWith: (props = {})->
modelProps = ()
obj = {}
for own prop, value of @
obj[prop] = value
for own prop, value of props
if value?
obj[prop] = value
else
delete obj[prop]
for entityProp in modelProps.getEntityProps()
entity = obj[entityProp]
subIdProp = modelProps.getIdPropByEntityProp(entityProp)
subId = obj[subIdProp]
if entity? and entity.id isnt subId
obj[subIdProp] = entity.id
modelProps = ()
return .createModel modelProps.modelName, obj
###*
Get diff prop values
getDiff
{any} plainObj
{Object} [options]
{Array(String)} [options.ignores] prop names to skip checking diff
{Object}
###
getDiff: (plainObj = {}, options = {}) ->
(plainObj, options).reduce (obj, prop) ->
obj[prop] = plainObj[prop]
return obj
, {}
###*
Get diff props
diff
{any} plainObj
{Object} [options]
{Array(String)} [options.ignores] prop names to skip checking diff
{Array(String)}
###
getDiffProps: (plainObj = {}, options = {}) ->
return Object.keys(@) if not plainObj? or typeof plainObj isnt 'object'
diffProps = []
modelProps = ()
ignores = {}
ignores[prop] = true for prop in options.ignores if Array.isArray(options.ignores)
propsToCheck = modelProps.getAllProps().filter (prop) ->
not ignores[prop] and not modelProps.isEntity(prop)
for prop in propsToCheck
thisValue = @[prop]
thatValue = plainObj[prop]
if not thisValue?
continue if not thatValue?
if not thatValue? or not thisValue?
diffProps.push(prop)
continue
continue if thisValue is thatValue
# if
continue if modelProps.isEntity(prop) and thisValue[prop]? and not thatValue?
if modelProps.isId(prop)
entityProp = modelProps.getEntityPropByIdProp(prop)
if thisValue isnt thatValue
diffProps.push(prop, entityProp)
continue
thisEntityValue = @[entityProp]
thatEntityValue = plainObj[entityProp]
if not thisEntityValue?
diffProps.push(entityProp) if thatEntityValue?
continue
else if typeof thisEntityValue.isDifferentFrom is 'function'
diffProps.push(entityProp) if thisEntityValue.isDifferentFrom(thatEntityValue)
continue
else
diffProps.push(entityProp) # rare case when value of entity prop isn't entity
else if modelProps.isDate(prop)
thisISOValue = if typeof thisValue.toISOString is 'function' then thisValue.toISOString() else thisValue
thatISOValue = if typeof thatValue.toISOString is 'function' then thatValue.toISOString() else thatValue
continue if thisISOValue is thatISOValue
else if modelProps.isEnum(prop)
thatEnumValue = if typeof thatValue is 'string' then (prop)[thatValue] else thatValue
continue if thisValue is thatEnumValue
else if typeof thisValue.isDifferentFrom is 'function'
continue if not thisValue.isDifferentFrom(thatValue)
else
continue if Util.deepEqual(thisValue, thatValue)
diffProps.push(prop)
return diffProps
###*
Get difference props
diff
{any} plainObj
{Array(String)}
###
isDifferentFrom: (val) ->
return (val).length > 0
###*
freeze the model
###
freeze: ->
throw ('FreezeMutableModel', 'Cannot freeze mutable model.') if not .isImmutable
return Object.freeze(@)
###*
include all relational models if not set
include
{Object} [options]
{Boolean} [options.async=true] get async values
{Array(String)} [options.props] include only given props
{Promise(BaseModel)} self
###
include: (options = {}) ->
Includer = require './includer'
new Includer(@, options).include().then => @
###*
include all relational models and returns new model
$include
{Object} [options]
{Boolean} [options.async=true] get async values
{Array(String)} [options.props] include only given props
{Promise(BaseModel)} new model
###
$include: (options = {}) ->
Includer = require './includer'
new Includer(@, options).include(createNew = true)
###*
Check if all subentities are included.
included
{Boolean}
###
included: (recursive = false) ->
modelProps = ()
for entityProp in modelProps.getEntityProps()
subIdProp = modelProps.getIdPropByEntityProp(entityProp)
return false if @[subIdProp]? and not @[entityProp]?
return true if not recursive
for modelProp in modelProps.models
return false if @[modelProp]? and not @[modelProp].included()
return true
module.exports = BaseModel