Skip to content

Commit

Permalink
refactor: allow for deep paths and merging
Browse files Browse the repository at this point in the history
  • Loading branch information
logicalparadox committed Sep 11, 2014
1 parent a6dca75 commit 07692f0
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 55 deletions.
83 changes: 56 additions & 27 deletions index.js
Expand Up @@ -4,6 +4,13 @@
* MIT Licensed
*/

/*!
* External dependencies
*/

var merge = require('params').merge;
var pathval = require('pathval');

/**
* ## Usage
*/
Expand Down Expand Up @@ -79,7 +86,7 @@
* @api public
*/

module.exports = function (proto, opts) {
module.exports = function(proto, opts) {
// handle second argument
if ('string' === typeof opts) {
opts = { store: opts };
Expand All @@ -91,25 +98,32 @@ module.exports = function (proto, opts) {

// fill in the blanks
if (!opts.store) opts.store = 'settings';
if (!opts.handle) opts.handle = function () {};
if (!opts.handle) opts.handle = function() {};

/**
* ## API
*/

/**
* #### .set (key[, value])
* #### .set (key[, value, force])
*
* Modify a key/value pair of settings, or use
* an object to modify many settings at once.
* Set an attribute in the `settings` of the internal store. Can `set` entire
* objects at a given address to be merged with existing object at the
* same address. `force` will replace a value regardless of merge scenario.
*
* ```js
* obj
* .set('hello', 'universe')
* .set({
* hello: 'universe'
* , say: 'loudly'
* // set at path
* obj.set('hello.target', 'world')
*
* // merge object
* obj.set({
* hello: {
* say: 'loudly'
* }
* });
*
* // force
* obj.set('hello', 'universe', true);
* ```
*
* @param {String|Object} key or object
Expand All @@ -118,23 +132,38 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.set = (function (opts) {
var handle = opts.handle
, prop = opts.store;
proto.set = (function(opts) {
var handle = opts.handle;
var prop = opts.store;

return function (key, value) {
return function(key, value, force) {
var settings = this[prop] || (this[prop] = {});

if (1 === arguments.length) {
if ('string' === typeof key) {
return settings[key];
} else {
for (var name in key) {
this.set(name, key[name]);
}
// .set('key.path') => .get alias
if (1 === arguments.length && 'string' === typeof key) {
return pathval.get(settings, key);

// .set(obj, true) => force overwrite
} else if ('object' === typeof key && value) {
var base = Array.isArray(key) ? [] : {};
settings = this[prop] = merge(base, key);

// .set(obj) => merge overwrite
} else if ('object' === typeof key) {
for (var name in key) {
this.set(name, key[name]);
}

// .set(key, obj) => merge at path
} else {
settings[key] = value;
if (!force) {
var tmp = pathval.get(settings, key);
value = tmp && 'object' === typeof tmp
? merge(tmp, value)
: value;
}

pathval.set(settings, key, value);
handle.call(this, key, value);
}

Expand All @@ -155,7 +184,7 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.get = function (key) {
proto.get = function(key) {
return this.set(key);
};

Expand All @@ -173,7 +202,7 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.enable = function (key) {
proto.enable = function(key) {
return this.set(key, true);
};

Expand All @@ -191,7 +220,7 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.disable = function (key) {
proto.disable = function(key) {
return this.set(key, false);
};

Expand All @@ -212,7 +241,7 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.enabled = function (key) {
proto.enabled = function(key) {
return !! this.get(key);
};

Expand All @@ -233,7 +262,7 @@ module.exports = function (proto, opts) {
* @api public
*/

proto.disabled = function (key) {
proto.disabled = function(key) {
return !!! this.get(key);
};

Expand Down
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -20,6 +20,8 @@
"test": "mocha"
},
"dependencies": {
"params": "^0.1.0",
"pathval": "^0.1.1"
},
"devDependencies": {
"chai": "*",
Expand Down
94 changes: 66 additions & 28 deletions test/facet.js
Expand Up @@ -29,28 +29,28 @@ function checkMethods (obj) {
* Tests
*/

describe('facet(obj)', function () {
it('should mixin prototypes', function () {
describe('facet(obj)', function() {
it('can mixin prototypes', function() {
var obj = new Obj();
checkMethods(obj);
});

it('should mixin objects', function () {
it('can mixin objects', function() {
var obj = {};
facet(obj);
checkMethods(obj);
});

it('should use \'settings\' as its property for storage', function () {
it('can use \'settings\' as its property for storage', function() {
var obj = new Obj();
obj.set('hello', 'universe');
obj.should.have.property('settings')
.and.have.property('hello', 'universe');
});
});

describe('facet(obj, \'options\')', function () {
it('should use \'options\' as its property for storage', function () {
describe('facet(obj, \'options\')', function() {
it('can use \'options\' as its property for storage', function() {
function Opts () {};
facet(Opts.prototype, 'options');
var opts = new Opts();
Expand All @@ -61,11 +61,11 @@ describe('facet(obj, \'options\')', function () {
});
});

describe('facet(obj, handle)', function () {
it('should invoke function for each set', function () {
describe('facet(obj, handle)', function() {
it('can invoke function for each set', function() {
function Opts () {};

var handle = chai.spy('handle', function (key, value) {
var handle = chai.spy('handle', function(key, value) {
this.should.be.instanceof(Opts);
checkMethods(this);
this.should.have.property('settings');
Expand All @@ -83,84 +83,122 @@ describe('facet(obj, handle)', function () {
});
});

describe('.set(key, value)', function () {
it('should modify a setting', function () {
describe('.set(key, value)', function() {
it('can modify a setting', function() {
var obj = new Obj();
obj.set('hello', 'universe');
obj.get('hello').should.equal('universe');
});

it('can modify a deep setting', function() {
var obj = new Obj();
obj.set('hello.universe', 'foo');
obj.get('hello').should.deep.equal({ universe: 'foo' });
});

it('throws on invalid merge scenario', function() {
var obj = new Obj();
obj.set('hello.universe', 'foo');
(function() {
obj.set('hello', [ 'foo' ]);
}).should.throw(/merge scenario/);
});
});

describe('.set(obj)', function () {
it('should modify multiple settings', function () {
describe('.set(obj)', function() {
it('can modify multiple settings', function() {
var obj = new Obj();
obj.set({ hello: 'universe', say: 'hello' });
obj.get('hello').should.equal('universe');
obj.get('say').should.equal('hello');
});

it('can deeply merge settings', function() {
var obj = new Obj();
obj.set({ foo: { bar: { hello: 'universe' }}});
obj.set({ foo: { baz: { hello: 'universe' }}});
obj.get('foo').should.deep.equal({
bar: { hello: 'universe' },
baz: { hello: 'universe' }
});
});

it('can forcefully replace settings', function() {
var obj = new Obj();
obj.set({ foo: { bar: { hello: 'universe' }}});
obj.set({ fun: { bar: { hello: 'world' }}}, true);
should.not.exist(obj.get('foo'));
obj.get('fun').should.deep.equal({ bar: { hello: 'world' }});
});
});

describe('.get(key)', function () {
it('should return an existing value', function () {
describe('.get(key)', function() {
it('returns an existing value', function() {
var obj = new Obj();
obj.set('hello', 'universe');
obj.get('hello').should.equal('universe');
});

it('should return undefined for values that do not exist', function () {
it('returns undefined for values that do not exist', function() {
var obj = new Obj();
should.equal(obj.get('nullll'), undefined);
});

it('return an existing value at a deep path', function() {
var obj = new Obj();
obj.set({ foo: { bar: [ 'hello', 'universe' ] }});
obj.get('foo.bar[1]').should.equal('universe');
});
});

describe('.enable(key)', function () {
it('should set a value to true', function () {
describe('.enable(key)', function() {
it('can set a value to true', function() {
var obj = new Obj();
obj.enable('hello');
obj.get('hello').should.be.true;
});
});

describe('.enabled(key)', function () {
describe('.enabled(key)', function() {
var obj = new Obj();
obj.enable('hello');
obj.disable('blah');

it('should return true when a value is true', function () {
it('returns true when a value is true', function() {
obj.enabled('hello').should.be.true;
});

it('should return false when a value is false', function () {
it('returns false when a value is false', function() {
obj.enabled('blah').should.be.false;
});

it('should return false for non-existent values', function () {
it('returns false for non-existent values', function() {
obj.enabled('noop').should.be.false;
});
});

describe('.disable(key)', function () {
it('should set a value to false', function () {
describe('.disable(key)', function() {
it('can set a value to false', function() {
var obj = new Obj();
obj.disable('hello');
obj.get('hello').should.be.false;
});
});

describe('.disabled(key)', function () {
describe('.disabled(key)', function() {
var obj = new Obj();
obj.enable('hello');
obj.disable('blah');

it('should return true when a value is false', function () {
it('returns true when a value is false', function() {
obj.disabled('blah').should.be.true;
});

it('should return true when a value does not exist', function () {
it('returns true when a value does not exist', function() {
obj.disabled('noop').should.be.true;
});

it('should return false when a value true', function () {
it('returns false when a value true', function() {
obj.disabled('hello').should.be.false;
});
});

0 comments on commit 07692f0

Please sign in to comment.