Mixin Guide

joshmbarney edited this page Sep 13, 2013 · 2 revisions

Mixin Guide

QuickConnect has several environments that are presented to the developer when building their program. The options argument in the QuickConnect constructor has a mixins object where you can define functions to augment the functionality of each of these levels.

'mixins' object

The mixins object has four fields: base, isolate, command, and control. These can optionally be defined by the developer as functions. These functions are called in the constructor of the environment object that is this in the various environments of QC.

Um, what?

So when you do

var runsmthng = qc.command('run something')

or

qc.command('run something', function(){
	var runsmthng = this
})

the variable runsmthng is an instance of QCCommandEnvironment. QCCommandEnvironment has a prototype that defines the standard methods of runsmthng: valcf, dcf, vcf, and dstack. The constructor actually looks like this:

function QCCommandEnvironment(fn) {
  if (fn) {
    fn.call(this)
  }
}

If you make a function at mixins.command, it will be passed to this constructor. This pattern is the same for all the mixins.

Environments

  • base is the QC instance you get from new QuickConnect(options)
  • isolate is the object you get from var iso = qc.isolate('iso')
  • command is the object you get from var com = (iso || qc).command('com')
  • control is the object this in a control function

Example

Here is an example from the QC tests that shows aliasing of the basic commands and attributes:

mixin = new QuickConnect({
    mixins: {
        base:function () {
            this.namespace = this.isolate
        },
        isolate:function () {
            this.addNamespace = this.isolate
            this.map = this.command
        },
        command:function () {
            this.validate = this.valcf
            this.data = this.dcf
            this.view = this.vcf
        },
        control:function () {
            this.go = this.STACK_CONTINUE
            this.asyncGo = this.asyncStackContinue
            this.wait = this.WAIT_FOR_DATA
        }
    }
})
module.exports = mixin
...
mixin.namespace('mixin')
.addNamespace('level 2')
.map('command')
.validate(function(){return true})
.data(function(){return this.go})
.data(function(data,qc){
    setTimeout(function(){qc.asyncGo()}, 50)
    return qc.wait
})
.view(function(data,qc){
    data.yay = true
    return qc.go
})

Better Example

qc = new QuickConnect({
    mixins: {
        command:function () {        
            this.isSet = function(key){
            	this.valcf(function(data,qc){
            		return data[key] || "data."+key+" is not set"
            	})
            }
            this.isSet = function(key,type){
            	this.valcf(function(data,qc){
            		var hold = data[key]
            		return hold && hold.constructor == type || "data."+key+" is not a "+type.name
            	})
            }
            
            this.set = function(key,gen){
            	this.dcf(function(data,qc){
            		data[key] = gen(data)
            		return qc.STACK_CONTINUE
            	})
            }
            this.if = function(check, func){
            	this.dcf(function(d,qc){
            		if(check(d)){
            			return func.apply(this,arguments)
            		} else {
            			return qc.STACK_CONTINUE
            		}
            	})
            }
        }
    }
})
...
qc.command('authenticate')
	.isSet('username')
	.isSet('password')
	.is('id',Number)
	
	.set("time",function(){return new Date})
	.dcf(contactServer)
	.if(response_2xx, praise_user('you are the bestest!'))

Here, in just a few lines of javascript, I was able to pull a bunch of flow control logic out of my functions and put it into the QC environment. Now I don't have to write a function that says 'praise user if 2xx response', I just wrote a praise_user function, and it I let QC determine if it should be called.

You can see on the last line that I called praise_user--It will generate a function that will be the second argument for the if mixin. Combining function generators with extracted control flow logic has proven to be an extreemly fast and convenient way to program. By writting my own mixins and generating functions for different cases, I can quickly tear down a stack of functions and rebuild it without needing to change the functions; just what is around it.

With mixins, QuickConnect will always be exactly what you need it to be.

Inheritance is not enough

If you subclass QuickConnect, you will be able to alter what is available on the QuickConnect instances, but you can't alter the environments that way.

Using the mixins is like subclassing the entire system (or parts of it) at once.

Please Use This

The mixins are intended to do more than just let you alias things so the standard QC vocab does not annoy you; they let you change the way you build things with qc to match your needs. If you are building a server, you could use methods like pathSection('users') and pathTerminator(':name') to define routes. If you don't want to return this.WAIT_FOR_DATA you can give yourself a method called async(someFunc) that wraps your function in one that will return the wait flag for you.

If you find that you have to call multiple async functions based on a list, you could define a helper function that tracks if everything has finished before moving on. If you want to log everything that happens, you could build automatic logging right into every control function.

The possibilities are endless.