Skip to content

Instantly share code, notes, and snippets.

@Fordi
Created January 31, 2012 14:56
Show Gist options
  • Save Fordi/1710904 to your computer and use it in GitHub Desktop.
Save Fordi/1710904 to your computer and use it in GitHub Desktop.
Rules engine for Javascript
/**
* 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