Skip to content

Instantly share code, notes, and snippets.

@kerbyfc
Last active August 29, 2015 14:22
Show Gist options
  • Save kerbyfc/9d7a41c0288b6fa06f8b to your computer and use it in GitHub Desktop.
Save kerbyfc/9d7a41c0288b6fa06f8b to your computer and use it in GitHub Desktop.
Blender.js (alfa) - expressed to resolve complex state consistency
(
(root, factory) ->
if typeof define is 'function' and define.amd
# AMD. Register as an anonymous module.
define [], factory
else if typeof exports is 'object'
# Node. Does not work with strict CommonJS, but
# only CommonJS-like environments that support module.exports,
# like Node.
module.exports = factory()
else
# Browser globals (root is window)
root.Blender = factory()
) this, ->
class Blender
###*
* Blender instancies
* @type { Array }
###
states = []
###*
* Instance counter
* @type { Number }
###
couter = 0
###*
* Rule types dict represents
* state consistency decision modifier
* @type { Object }
###
ruleTypes:
consistent: true
inconsistent: false
###*
* Methods that should be used as proxy
* @type {Array}
###
interface: [
"blend"
"isConsistent"
"isntConsistent"
]
###*
* Setup blender instance
* @param { Object } bootstrap
###
constructor: (bootstrap) ->
@rules = {}
@data = {}
@_instance = couter++
states[@_instance] = {}
for ruleType in Object.keys @ruleTypes
# default
@rules[ruleType] = []
# check passed config
if typeof bootstrap[ruleType] is "object"
@rules[ruleType] = Array.prototype.map.call bootstrap[ruleType], (rule) ->
unless rule instanceof RegExp
return new RegExp rule
rule
# cut rules
delete bootstrap[ruleType]
# all other properties represents state
@options = bootstrap
###*
* @example
* if state.set({...}).consistent
* model.set state.get()
*
* @param { Object } data - new data for state
* @param { Object } options = force: false
###
set: (data, options = force: false) ->
mix = @blend data
# do not change state for safe mode
unless options.force or mix.consistent
return mix
states[@_instance] = mix
mix
###*
* Get copy of state
* @param { String } key
* @return { Any } data
###
get: (key) ->
state = {}
state[key] = val for key, val of states[@_instance]
unless key
state.data
else
state.data[key]
###*
* Blend new mix and check it's consistence
* @param { Object } data ingredients
* @return { Object } state
###
blend: (data) ->
state =
consistent : false
data : data
inconsistency : []
positive = true
inconsistency = state.inconsistency
for type, mod of @ruleTypes
for rule in @rules[type]
# accumulate matching data
report =
options : []
values : []
summary : {}
type : type
rule : rule.toString()
# search matches for each data value
for key, value of state.data
if rule.test value
report.options.push key
report.values.push value
report.summary[key] = value
# if there are few options matched
# state becomes inconsistent for proper matching type
found = report.values.length > 1
# by default state is consistent,
# positive rules are the first and if
# such rule was mached, we mark
# state as consistent and break
# positive rules mathing cycle
if mod is positive
if found
state.consistent = true
# break positive rules mathing cycle
break
else
# negative matches are after positive
if found
inconsistency.push report
state.consistent = false
unless state.inconsistency.length
delete state.inconsistency
state
###*
* Determine if data is consistent
* @param { Object } data ingredients
* @return { Boolean } consistent decision
###
isConsistent: (data = @get().data) ->
@blend data
.consistent
###*
* Determine if data is NOT consistent
* @param { Object } data ingredients
* @return { Boolean } consistent decision
###
isntConsistent: (data = @get().data) ->
not @blend data
.consistent
###*
* Create proxy methods for target object
* @param {Object} target
* @return {Object} target with blender methods
###
bridge: (target) ->
for method in @interface
do (method) =>
unless target[method]
target[method] = =>
@[method] arguments...
target
describe "Blender", ->
before ->
@process = (data) ->
state = @blender.blend(data)
console.log JSON.stringify state, null, 4
state
@blender = new Blender
consistent: [
# show "all" violation level option only for line view
///
line # view
|Levels.all # option
///
# show some options only with proper view types
///
line # view
|column_stacked # view
|person_activity # type
|count
|groupingByPeriod # option
|Levels.(high|low|medium|no) # option(s)
///
# show some options only for non-dynamic charts
///
showOthersGroup # option
|showValues # option
|showPercentage # option
|bar # view(s)
|pie # view
///
///
bar # view
|pie # view
|object_type_code # type
|category # type
|protected_document # type
|protected_catalog # type
|object_to_list # type
|policy # type
|sender_receiver # type
|sender # type
|receiver # type
|workstation # type
|web_resources # type
|user_decision # type
///
]
inconsistent: [
# disable bar & pie for charts that represents dynamic
///
person_activity # type
|bar # view
|pie # view
///
# dont show some options for column & line
///
column_stacked # view
|line # view
|limit # option
|showValues # option
///
# dont show limit for some types
///
limit # option
|object_type_code # type
|user_decision # type
///
]
it "1. should determine state inconsistence", ->
@process
type: "protected_document"
view: "bar_grouped"
.consistent.should.be.ok
it "2. should determine state inconsistence", ->
consistence = @process type: "person_activity", view: "bar_grouped"
consistence.consistent.should.not.be.ok
consistence.should.be.eql({
"consistent": false,
"data": {
"type": "person_activity",
"view": "bar_grouped"
},
"inconsistency": [
{
"options": [
"type",
"view"
],
"values": [
"person_activity",
"bar_grouped"
],
"summary": {
"type": "person_activity",
"view": "bar_grouped"
},
"type": "inconsistent",
"rule": "/person_activity|bar|pie/"
}
]
})
it "3. should determine state inconsistence", ->
@process type: "person_activity", view: "line"
.consistent.should.be.ok
consistence = @process type: "protected_document", view: "column_stacked"
consistence.consistent.should.not.be.ok
consistence.should.be.eql({
"consistent": false,
"data": {
"type": "protected_document",
"view": "column_stacked"
}
})
it "should determine state consistence", ->
consistence = @process
type: "person_activity"
view: "line"
option: "count"
consistence.should.eql({
"consistent": true,
"data": {
"type": "person_activity",
"view": "line",
"option": "count"
}
})
consistence.consistent.should.be.ok
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment