├── .gitignore ├── buster.js ├── package.json ├── simplified-prod-creator.js ├── README.md ├── creator.js └── test └── creator-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /buster.js: -------------------------------------------------------------------------------- 1 | var config = module.exports; 2 | 3 | config["Server tests"] = { 4 | tests: ["test/**/*-test.js"], 5 | environment: "node" 6 | }; 7 | 8 | config["Browser tests"] = { 9 | sources: ["creator.js"], 10 | tests: ["test/**/*-test.js"], 11 | environment: "browser" 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "creator", 3 | "version": "0.2.0", 4 | "main": "./creator", 5 | "description": "A tiny library for creating create-methods for your objects.", 6 | "author": "Magnar Sveen ", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/magnars/creator.js.git" 10 | }, 11 | "homepage": "https://github.com/magnars/creator.js", 12 | "scripts": { 13 | "test": "./node_modules/buster/bin/buster-test" 14 | }, 15 | "devDependencies": { "buster": "*" }, 16 | "engines": { "node": ">=0.6" }, 17 | "keywords": ["create"], 18 | "licenses": [{ 19 | "type": "BSD 2-Clause license", 20 | "url": "http://www.opensource.org/licenses/bsd-license.php" 21 | }] 22 | } 23 | -------------------------------------------------------------------------------- /simplified-prod-creator.js: -------------------------------------------------------------------------------- 1 | var creator = (function () { 2 | var slice = Array.prototype.slice; 3 | 4 | var extend = function (obj) { 5 | var i, l, sources = slice.call(arguments, 1); 6 | for (i = 0, l = sources.length; i < l; i += 1) { 7 | for (var prop in sources[i]) { 8 | obj[prop] = sources[i][prop]; 9 | } 10 | } 11 | return obj; 12 | }; 13 | 14 | function partial(fn) { 15 | var args = slice.call(arguments, 1); 16 | return function () { 17 | return fn.apply(this, args.concat(slice.call(arguments))); 18 | }; 19 | } 20 | 21 | function F() {} 22 | 23 | function create(o) { 24 | F.prototype = o; 25 | return new F(); 26 | } 27 | 28 | var createAndExtend = function (defaults, params) { 29 | return extend(create(this), defaults, params); 30 | }; 31 | 32 | return function (name, options) { 33 | return partial(createAndExtend, options.defaults); 34 | }; 35 | }()); 36 | 37 | if (typeof require === "function" && typeof module !== "undefined") { 38 | module.exports = creator; 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # creator.js 2 | 3 | A tiny library for creating create-methods for your objects. 4 | 5 | These are some things I like: 6 | 7 | * Using `Object.create`. 8 | * Having objects with their own `create` method. 9 | * Create-methods that take named parameters. 10 | * Failing early. 11 | 12 | Creator.js helps with this. Instead of writing: 13 | 14 | var banana = { 15 | create: function (params) { 16 | if (!params) { throw new TypeError("banana.create: missing params { color, brand }"); } 17 | if (!params.color) { throw new TypeError("banana.create: missing param { color }"); } 18 | if (!params.brand) { throw new TypeError("banana.create: missing param { brand }"); } 19 | 20 | return Object.extend(Object.create(this), params); 21 | } 22 | }; 23 | 24 | You have: 25 | 26 | var banana = { 27 | create: creator("banana", ["color", "brand"]) 28 | }; 29 | 30 | Or if you need more options: 31 | 32 | var banana = { 33 | create: creator("banana", { 34 | required: ["color", "brand"], 35 | defaults: { curvature: 23 }, 36 | strict: true 37 | } 38 | }; 39 | 40 | In which 41 | 42 | * `required` is a list of parameters that are required, 43 | * `defaults` is a map of parameter:value pairs that are optional with defaults, and 44 | * `strict` makes it complain about unknown parameters. 45 | 46 | ## Won't someone think of the performance 47 | 48 | In a production environment, you can use the simplified version of creator, 49 | which works like this: 50 | 51 | var create = function (defaults, params) { 52 | return extend(Object.create(this), defaults, params); 53 | }; 54 | 55 | var creator = function (name, options) { 56 | return partial(create, options.defaults); 57 | }; 58 | 59 | That way you get meaningful error messages and early failures while developing 60 | and testing, without sacrificing performance in production. 61 | 62 | In areas of code that needs to be highly optimized, you should of course use 63 | whatever is optimized better by current JavaScript engines. At the time of 64 | writing that would be the pseudo-classical inheritance style, with function 65 | constructors and `new`. 66 | 67 | ## License 68 | 69 | BSD 2-clause license. http://www.opensource.org/licenses/bsd-license.php 70 | -------------------------------------------------------------------------------- /creator.js: -------------------------------------------------------------------------------- 1 | var creator = (function (undefined) { 2 | var arrayProto = Array.prototype; 3 | var slice = arrayProto.slice; 4 | 5 | var isArray = Array.isArray || function (value) { 6 | return Object.prototype.toString.call(value) == "[object Array]"; 7 | }; 8 | 9 | var keys = Object.keys || function (obj) { 10 | var keys = []; 11 | for (var key in obj) { 12 | if (obj.hasOwnProperty(key)) { 13 | keys[keys.length] = key; 14 | } 15 | } 16 | return keys; 17 | }; 18 | 19 | var extend = function (obj) { 20 | var i, l, sources = slice.call(arguments, 1); 21 | for (i = 0, l = sources.length; i < l; i += 1) { 22 | for (var prop in sources[i]) { 23 | obj[prop] = sources[i][prop]; 24 | } 25 | } 26 | return obj; 27 | }; 28 | 29 | var indexOf = arrayProto.indexOf || function (item) { 30 | var i, l; 31 | for (i = 0, l = this.length; i < l; i++) { 32 | if (i in this && this[i] === item) { 33 | return i; 34 | } 35 | } 36 | return -1; 37 | }; 38 | 39 | function difference(a1, a2) { 40 | var i, l, d = []; 41 | for (i = 0, l = a1.length; i < l; i++) { 42 | if (indexOf.call(a2, a1[i]) < 0) { 43 | d[d.length] = a1[i]; 44 | } 45 | } 46 | return d; 47 | } 48 | 49 | function F() {} 50 | 51 | function create(o) { 52 | F.prototype = o; 53 | return new F(); 54 | } 55 | 56 | function partial(fn) { 57 | var args = slice.call(arguments, 1); 58 | return function () { 59 | return fn.apply(this, args.concat(slice.call(arguments))); 60 | }; 61 | } 62 | 63 | function createAndExtend(defaults, params) { 64 | return extend(create(this), defaults, params); 65 | } 66 | 67 | function throwError(name, msg) { 68 | throw new TypeError(name + ".create: " + msg); 69 | } 70 | 71 | function checkRequired(required, create, fail) { 72 | return function (params) { 73 | var missing = params ? difference(required, keys(params)) : required; 74 | if (missing.length) { 75 | fail("missing params { " + missing.join(", ") + " }"); 76 | } else { 77 | return create.call(this, params); 78 | } 79 | }; 80 | } 81 | 82 | function beStrict(required, defaults, create, fail) { 83 | var known = required.concat(keys(defaults)); 84 | return function (params) { 85 | var unknown = params ? difference(keys(params), known) : []; 86 | if (unknown.length) { 87 | fail("unknown params { " + unknown.join(", ") + " }"); 88 | } else { 89 | return create.call(this, params); 90 | } 91 | }; 92 | } 93 | 94 | function sanitize(options) { 95 | if (isArray(options)) { 96 | options = { required: options }; 97 | } 98 | options.required = options.required || []; 99 | options.defaults = options.defaults || {}; 100 | return options; 101 | } 102 | 103 | function creator(name, options) { 104 | options = sanitize(options); 105 | 106 | var create = partial(createAndExtend, options.defaults); 107 | var fail = partial(throwError, name); 108 | if (options.strict) { 109 | create = beStrict(options.required, options.defaults, create, fail); 110 | } 111 | if (options.required) { 112 | create = checkRequired(options.required, create, fail); 113 | } 114 | return create; 115 | } 116 | 117 | return creator; 118 | }()); 119 | 120 | if (typeof require === "function" && typeof module !== "undefined") { 121 | module.exports = creator; 122 | } 123 | -------------------------------------------------------------------------------- /test/creator-test.js: -------------------------------------------------------------------------------- 1 | if (typeof require === "function" && typeof module !== "undefined") { 2 | var buster = require("buster"); 3 | var creator = require("../creator"); 4 | } 5 | 6 | (function () { 7 | "use strict"; 8 | 9 | buster.testCase('Creator', { 10 | "creates a function that creates objects that": { 11 | setUp: function () { 12 | this.banana = { 13 | create: creator("banana", []), 14 | color: "green" 15 | }; 16 | }, 17 | 18 | "inherit from this": function () { 19 | var obj = this.banana.create(); 20 | assert.equals(obj.color, "green"); 21 | }, 22 | 23 | "changes independently of this": function () { 24 | var obj = this.banana.create(); 25 | obj.color = "brown"; 26 | assert.equals(this.banana.color, "green"); 27 | }, 28 | 29 | "is extended with the given parameters": function () { 30 | var obj = this.banana.create({ curvature: 23 }); 31 | assert.equals(obj.curvature, 23); 32 | } 33 | }, 34 | 35 | "required parameters": { 36 | setUp: function () { 37 | this.banana = { 38 | create: creator("banana", { 39 | required: ["color", "curvature"] 40 | }) 41 | }; 42 | }, 43 | 44 | "stay quiet when all are present": function () { 45 | refute.exception(function () { 46 | this.banana.create({ color: "brown", curvature: "42" }); 47 | }.bind(this)); 48 | }, 49 | 50 | "complain when all params are missing": function () { 51 | try { 52 | this.banana.create(); 53 | } catch (e) { 54 | var expected = "banana.create: missing params { color, curvature }"; 55 | assert.equals(e.message, expected); 56 | } 57 | }, 58 | 59 | "complain when some params are missing": function () { 60 | try { 61 | this.banana.create({ color: "green" }); 62 | } catch (e) { 63 | var expected = "banana.create: missing params { curvature }"; 64 | assert.equals(e.message, expected); 65 | } 66 | }, 67 | 68 | "has a shorthand API": function () { 69 | var banana = { 70 | create: creator("banana", ["color", "curvature"]) 71 | }; 72 | try { 73 | banana.create({ color: "green" }); 74 | } catch (e) { 75 | var expected = "banana.create: missing params { curvature }"; 76 | assert.equals(e.message, expected); 77 | } 78 | } 79 | }, 80 | 81 | "default parameters": { 82 | setUp: function () { 83 | this.banana = { 84 | create: creator("banana", { 85 | defaults: { color: "green", curvature: 23 } 86 | }) 87 | }; 88 | }, 89 | 90 | "are added": function () { 91 | assert.equals(this.banana.create().color, "green"); 92 | }, 93 | 94 | "are overwritten": function () { 95 | var b = this.banana.create({ color: "yellow" }); 96 | assert.equals(b.color, "yellow"); 97 | } 98 | }, 99 | 100 | "when strict": { 101 | setUp: function () { 102 | this.banana = { 103 | create: creator("banana", { 104 | required: ["color"], 105 | defaults: { curvature: 23 }, 106 | strict: true 107 | }) 108 | }; 109 | }, 110 | 111 | "complains about unknown parameters": function () { 112 | try { 113 | this.banana.create({ 114 | color: "brown", 115 | curvature: 42, 116 | musical: "Which Witch" 117 | }); 118 | } catch (e) { 119 | var expected = "banana.create: unknown params { musical }"; 120 | assert.equals(e.message, expected); 121 | } 122 | } 123 | } 124 | 125 | }); 126 | }()); 127 | --------------------------------------------------------------------------------