Skip to content

Instantly share code, notes, and snippets.

@jtacoma
Created February 4, 2011 22:09
Show Gist options
  • Save jtacoma/811874 to your computer and use it in GitHub Desktop.
Save jtacoma/811874 to your computer and use it in GitHub Desktop.
One way to translate a spec(ification), that is a plain old object, into a proper class. The resulting class is to be instantiated from an object with a fixed set of properties (e.g. a table row) and to calculate other properties based on those received.
// Given a prototype-like object, create a class based on that object's
// properties.
function buildClass(spec) {
// Throw an exception of the argument is inappropriate:
if (typeof(spec) != 'object' || spec === null)
throw 'expected an object.';
var empty = true;
for (var name in spec) { empty = false; break; }
if (empty)
throw 'expected a non-empty object.';
// Define a constructor that will set all owned properties based on the
// spec (as a prototype-like object) and the arguments passed in to the
// constructor:
var constructor = function (args) {
for (var name in spec)
if (typeof(spec[name]) != 'undefined' && spec[name].hasOwnProperty('prototype'))
// A function on the spec is evaluated over the object thus far
// defined, and the return value is stored on the object:
this[name] = spec[name](this);
else if (typeof(spec[name]) != 'undefined')
// All other defined properties on the spec are 'static' fields:
continue;
else if (!(name in args) || typeof(args[name]) == 'undefined')
// An undefined property on the spec indicates a required argument
// to the constructor:
throw 'argument contains no such property: ' + name;
else
// Copy supplied properties matching spec into the new object:
this[name] = args[name];
};
// Set constant 'static' fields on the prototype:
for (var name in spec)
if (typeof(spec[name]) != 'undefined')
constructor.prototype[name] = spec[name];
// Finally, we're done:
return constructor;
}
exports.buildClass = buildClass;
var vows = require('vows'),
assert = require('assert');
var lib = require('./json2class.js');
vows.describe('json2class').addBatch({
'Attempting to build a class from...': {
'undefined must throw an exception.': function() {
assert.throws (function () { lib.buildClass(); });
assert.throws (function () { lib.buildClass(undefined); });
},
'null must throw an exception.': function() {
assert.throws (function () { lib.buildClass(null); });
},
'number must throw an exception.': function() {
assert.throws (function () { lib.buildClass(42); });
},
'string must throw an exception.': function() {
assert.throws (function () { lib.buildClass('a string'); });
},
'an empty object must throw an exception.': function() {
assert.throws (function () { lib.buildClass({}); });
},
},
'A class built from an object with an undefined property, a defined property, and a function...': {
topic: function() {
var cls = lib.buildClass({
a: undefined,
b: 'defined',
c: function(obj) { return '(' + obj.a + ',' + obj.b + ')' },
});
return cls;
},
'must be callable.': function(topic) {
assert.ok (topic);
assert.ok ('call' in topic);
},
'instantiated with no arguments, must throw an exception.': function(topic) {
assert.throws (function () { new topic(); });
},
'instantiated with an argument, must copy a value for the spec\'s undefined property from that argument.': function(topic) {
assert.equal (new topic({a:1}).a, 1);
},
'instantiated with an argument that has no such property, must throw an exception.': function(topic) {
assert.throws (function() { new topic({}); });
},
'instantiated with an argument that has a value for the defined property, must ignore that value.': function(topic) {
assert.equal (new topic({a:1,b:2}).b, 'defined');
},
'instantiated, must call the spec\'s function to determine a value for the associated property.': function(topic) {
assert.equal (new topic({a:1,b:2}).c, '(1,defined)');
},
}
}).run();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment