diff --git a/lib/utils.js b/lib/utils.js index 2df38af..1d5dcfb 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -183,6 +183,61 @@ var Utils = module.exports = { }; }, + // Public: Analogue to Ruby's Hash#fetch method for looking up object + // properties. + // + // obj - object to do property lookup on + // property - string property name to attempt to look up + // fallback - either: + // - a fallback value to return if `property` can't be found + // - a function to be executed if `property` can't be found. The function + // will be passed `property` as an argument. + // + // Examples + // + // var object = { property: "hello world" }; + // fetch(object, "property"); + // //=> "hello world" + // + // fetch(object, "notaproperty", "default value"); + // //=> "default value" + // + // var notFound = function(prop) { return prop + " not found!" }; + // fetch(object, "notaproperty", notFound) + // // "notaproperty not found!" + // + // var badFallback = function(prop) { prop + " not found!" }; + // fetch(object, "notaproperty", badFallback) + // // Error: no return value from provided callback function + // + // fetch(object, "notaproperty"); + // // Error: key not found: "notaproperty" + // + // Returns the value of obj[property], a fallback value, or the results of + // running 'fallback'. If the property isn't found, and 'fallback' hasn't been + // provided, will throw an error. + fetch: function(obj, property, fallback) { + if (obj.hasOwnProperty(property)) { + return obj[property]; + } + + if (fallback === void 0) { + throw new Error('key not found: "' + property + '"'); + } + + if (typeof(fallback) === 'function') { + var value = fallback(property); + + if (value === void 0) { + throw new Error('no return value from provided fallback function'); + } + + return value; + } + + return fallback; + }, + // Public: Adds necessary utils to global namespace, along with base class // extensions. // diff --git a/test/specs/utils.spec.js b/test/specs/utils.spec.js index 30733d4..066b0f4 100644 --- a/test/specs/utils.spec.js +++ b/test/specs/utils.spec.js @@ -222,4 +222,52 @@ describe("Utils", function() { expect(proxy.boundMethod("Hello", "World")).to.eql(["Hello", "World"]); }) }); + + describe("#fetch", function() { + var fetch = utils.fetch, + obj = { property: 'hello world', 'false': false, 'null': null }; + + context("if the property exists on the object", function() { + it("returns the value", function() { + expect(fetch(obj, 'property')).to.be.eql('hello world'); + expect(fetch(obj, 'false')).to.be.eql(false); + expect(fetch(obj, 'null')).to.be.eql(null); + }); + }); + + context("if the property doesn't exist on the object", function() { + context("and no fallback value has been provided", function() { + it("throws an Error", function() { + var fn = function() { return fetch(obj, "notaproperty"); }; + expect(fn).to.throw(Error, 'key not found: "notaproperty"'); + }); + }); + + context("and a fallback value has been provided", function() { + it('returns the fallback value', function() { + expect(fetch(obj, 'notakey', 'fallback')).to.be.eql('fallback'); + }); + }); + + context("and a fallback function has been provided", function() { + context("if the function has no return value", function() { + it("throws an Error", function() { + var fn = function() { fetch(obj, 'notakey', function() {}); }, + str = 'no return value from provided fallback function'; + + expect(fn).to.throw(Error, str); + }); + }); + + context("if the function returns a value", function() { + it("returns the value returned by the fallback function", function() { + var fn = function(key) { return "Couldn't find " + key }, + value = "Couldn't find notakey"; + + expect(fetch(obj, 'notakey', fn)).to.be.eql(value); + }); + }); + }); + }); + }); });