Created
January 31, 2012 14:56
-
-
Save Fordi/1710904 to your computer and use it in GitHub Desktop.
Rules engine for Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Simple javascript rules engine | |
* | |
* @author Bryan Elliott | |
* @link http://codemonkeybryan.com | |
* | |
* A RuleSet is essentially a simple list of voters. By executing the RuleSet, | |
* you iterate over the voters and obtain a done/fail event via jQuery's Deferred | |
* interface. | |
* | |
* Voters are methods of the form "function (token, vote) {" where "this" is the owning object, and "token" | |
* is an object that can be passed in at validation time. "vote" is a method the allows the Voter to | |
* asynchronously cast its vote. If the voter wishes to operate synchronously, it can simply return a | |
* boolean or Override. | |
* | |
* Votes come in three flavors: true (done), false (fail), or RuleSet.Override (force-done). In the | |
* normal case, all votes must be true for a vote to be done. If a RuleSet.Override is voted in any | |
* case, however, the vote is always done. | |
* | |
* The token object is persistent; it's shared between the rules and the done/fail. The rules are not | |
* processed in any specific order, but are forked off at call time. | |
* | |
* The done/fail methods are provided with "this" as the owning object and a "token" argument corresponding | |
* to the token passed through all the calls. | |
**/ | |
var RuleSet = (function () { | |
function CONST(name) { | |
var ret = function () {}; | |
ret.toString = function (context) { | |
return name; | |
} | |
return ret; | |
} | |
var RuleSet = function () { | |
if (!(this instanceof arguments.callee)) | |
throw new Error("RuleSet defines a class; please instantiate it"); | |
var inst = this, | |
invoke = function (context) { | |
var i, | |
target = this, | |
endResult = true, | |
count = inst.voters.length, | |
def = new $.Deferred(); | |
for (i = 0; i < inst.voters.length; i++) | |
(function (voter) { | |
setTimeout(function () { | |
var vote = function (result) { | |
if (result === false && endResult !== RuleSet.Override) | |
endResult = false; | |
else if (result === RuleSet.Override) | |
endResult = RuleSet.Override; | |
if (--count > 0) | |
return; | |
if (endResult) | |
def.resolveWith(target, [context]); | |
else | |
def.rejectWith(target, [context]); | |
}; | |
var ret = voter.call(target, context, vote); | |
if (!(typeof ret == undefined)) | |
vote(ret); | |
}, 1); | |
}(inst.voters[i])); | |
return def.promise(); | |
}; | |
inst.voters = []; | |
invoke.addVoter = function (voter) { | |
for (var i=0; i<arguments.length; i++) | |
inst.voters.push(arguments[i]); | |
return invoke; | |
}; | |
return invoke; | |
}; | |
RuleSet.Override = CONST("Override Past Rejections"); | |
return RuleSet; | |
}()); | |
/** | |
* Small test-suite for RuleSet | |
**/ | |
RuleSet.test = function () { | |
function CONST(name) { | |
var ret = function () {}; | |
ret.toString = function (context) { | |
return name; | |
} | |
return ret; | |
} | |
var TheClass = function () { | |
}; | |
TheClass.isValid = TheClass.prototype.isValid = new RuleSet(); | |
TheClass.isValid | |
.addVoter(function (context, vote) { | |
return !!this.canPass; | |
}) | |
.addVoter(function (context, vote) { | |
vote(this.isCorrect); | |
}); | |
var myObject = new TheClass(); | |
var sequentiallyExecute = function () { | |
var successMethods = []; | |
for (i=0; i<arguments.length; i++) (function (method, index) { | |
successMethods[index] = function () { | |
method(successMethods[index+1]||function () {}); | |
}; | |
}(arguments[i], i)); | |
successMethods[0](); | |
} | |
sequentiallyExecute(function testDone(success) { | |
myObject.canPass = true; | |
myObject.isCorrect = true; | |
myObject.isValid() | |
.done(function () { | |
console.log("RuleSet passed basic sanity check (done)"); | |
success(); | |
}) | |
.fail(function () { | |
throw new Error("RuleSet failed basic sanity check (done)"); | |
}); | |
}, | |
function testFailed(success) { | |
myObject.isCorrect = true; | |
myObject.canPass = false; | |
myObject.isValid() | |
.done(function () { | |
throw new Error("RuleSet failed basic sanity check (fail)"); | |
}) | |
.fail(function () { | |
console.log("RuleSet passed basic sanity check (fail)"); | |
success(); | |
}); | |
}, | |
function testOverride(success) { | |
myObject.isCorrect = RuleSet.Override; | |
myObject.canPass = false; | |
myObject.isValid() | |
.done(function () { | |
console.log("RuleSet passed Override check"); | |
success(); | |
}) | |
.fail(function () { | |
throw new Error("RuleSet failed Override check"); | |
}); | |
}, | |
function testTokenPassage(success) { | |
var testRules = new RuleSet(), | |
testToken = CONST("Token for passage"); | |
testRules.addVoter(function (token, vote) { | |
return token===testToken; | |
}); | |
testRules(testToken) | |
.done(function (token) { | |
console.log("RuleSet passed token-voter passage check"); | |
if (token !== testToken) | |
throw new Error("RuleSet failed token-callback passage check"); | |
else | |
console.log("RuleSet passed token-callback passage check"); | |
success(); | |
}) | |
.fail(function () { | |
throw new Error("RuleSet failed token-voter passage check"); | |
}); | |
} | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment