Skip to content

Commit

Permalink
[api] Added .merge() to stores.Memory and stores.Redis
Browse files Browse the repository at this point in the history
  • Loading branch information
indexzero committed Jun 5, 2011
1 parent a4f00be commit 4459ba5
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 20 deletions.
53 changes: 51 additions & 2 deletions lib/nconf/stores/memory.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,13 @@ Memory.prototype.set = function (key, value) {
//
while (path.length > 1) {
key = path.shift();
if (!target[key]) {
if (!target[key] || typeof target[key] !== 'object') {
target[key] = {};
}

target = target[key];
}

// Set the specified value in the nested JSON structure
key = path.shift();
target[key] = value;
Expand Down Expand Up @@ -112,6 +112,55 @@ Memory.prototype.clear = function (key) {
return true;
};

Memory.prototype.merge = function (key, value) {
//
// If the key is not an `Object` or is an `Array`,
// then simply set it. Merging is for Objects.
//
if (typeof value !== 'object' || Array.isArray(value)) {
return this.set(key, value);
}

var self = this,
target = this.store,
path = nconf.path(key),
fullKey = key;

//
// Update the `mtime` (modified time) of the key
//
this.mtimes[key] = Date.now();

//
// Scope into the object to get the appropriate nested context
//
while (path.length > 1) {
key = path.shift();
if (!target[key]) {
target[key] = {};
}

target = target[key];
}

// Set the specified value in the nested JSON structure
key = path.shift();

//
// If the current value at the key target is not an `Object`,
// of is an `Array` then simply override it because the new value
// is an Object.
//
if (typeof target[key] !== 'object' || Array.isArray(target[key])) {
target[key] = value;
return true;
}

return Object.keys(value).every(function (nested) {
return self.merge(fullKey + ':' + nested, value[nested]);
});
};

//
// ### function reset (callback)
// Clears all keys associated with this instance.
Expand Down
80 changes: 66 additions & 14 deletions lib/nconf/stores/redis.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,19 +132,7 @@ Redis.prototype.set = function (key, value, callback) {
// Set the callback if not provided for "fire and forget"
callback = callback || function () { };

function addKey (partial, next) {
var index = path.indexOf(partial),
base = [self.namespace].concat(path.slice(0, index)),
parent = nconf.key.apply(null, base.concat(['keys']));

self.redis.sadd(parent, partial, next);
};

//
// Iterate over the entire key path and add each key to the
// parent key-set if it doesn't exist already.
//
async.forEach(path, addKey, function (err) {
this._addKeys(key, function (err) {
if (err) {
return callback(err);
}
Expand All @@ -159,20 +147,65 @@ Redis.prototype.set = function (key, value, callback) {
// (i.e. If you set and Object then wish to later retrieve only a
// member of that Object, the entire Object need not be retrieved).
//
self.cache.set(key, value);
self._setObject(fullKey, value, callback);
}
else {
//
// If the value is a simple literal (or an `Array`) then JSON
// stringify it and put it into Redis.
//
value = JSON.stringify(value);
self.cache.set(key, value);
value = JSON.stringify(value);
self.redis.set(fullKey, value, callback);
}
});
};

Redis.prototype.merge = function (key, value, callback) {
//
// If the key is not an `Object` or is an `Array`,
// then simply set it. Merging is for Objects.
//
if (typeof value !== 'object' || Array.isArray(value)) {
return this.set(key, value, callback);
}

var self = this,
path = nconf.path(key),
fullKey = nconf.key(this.namespace, key);

// Set the callback if not provided for "fire and forget"
callback = callback || function () { };

//
// Get the set of all children keys for the `key` supplied. If the value
// to be returned is an Object, this list will not be empty.
//
this._addKeys(key, function (err) {
self.redis.smembers(nconf.key(fullKey, 'keys'), function (err, keys) {
function nextMerge (nested, next) {
var keyPath = nconf.key.apply(null, path.concat([nested]));
self.merge(keyPath, value[nested], next);
}

if (keys && keys.length > 0) {
//
// If there are existing keys then we must do a recursive merge
// of the two Objects.
//
return async.forEach(Object.keys(value), nextMerge, callback);
}

//
// Otherwise, we can simply invoke `set` to override the current
// literal or Array value with our new Object value
//
self.set(key, value, callback);
});
});
};

//
// ### function clear (key, callback)
// #### @key {string} Key to remove from this instance
Expand Down Expand Up @@ -325,6 +358,25 @@ Redis.prototype.reset = function (callback) {
});
};

Redis.prototype._addKeys = function (key, callback) {
var self = this,
path = nconf.path(key);

function addKey (partial, next) {
var index = path.indexOf(partial),
base = [self.namespace].concat(path.slice(0, index)),
parent = nconf.key.apply(null, base.concat(['keys']));

self.redis.sadd(parent, partial, next);
};

//
// Iterate over the entire key path and add each key to the
// parent key-set if it doesn't exist already.
//
async.forEach(path, addKey, callback);
};

//
// ### @private function _setObject (key, value, callback)
// #### @key {string} Key to set in this instance
Expand Down
11 changes: 10 additions & 1 deletion test/fixtures/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ exports.data = {
password: 'password'
}
}
}
};

exports.merge = {
prop1: 1,
prop2: [1, 2, 3],
prop3: {
foo: 'bar',
bar: 'foo'
}
};
37 changes: 35 additions & 2 deletions test/memory-store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

var vows = require('vows'),
assert = require('assert'),
nconf = require('../lib/nconf');

nconf = require('../lib/nconf'),
merge = require('./fixtures/data').merge;

vows.describe('nconf/stores/memory').addBatch({
"When using the nconf memory store": {
topic: new nconf.stores.Memory(),
Expand Down Expand Up @@ -46,6 +47,38 @@ vows.describe('nconf/stores/memory').addBatch({
assert.isTrue(store.clear('foo:bar:bazz'));
assert.isTrue(typeof store.get('foo:bar:bazz') === 'undefined');
}
},
"the merge() method": {
"when overriding an existing literal value": function (store) {
store.set('merge:literal', 'string-value');
store.merge('merge:literal', merge);
assert.deepEqual(store.get('merge:literal'), merge);
},
"when overriding an existing Array value": function (store) {
store.set('merge:array', [1,2,3,4]);
store.merge('merge:array', merge);
assert.deepEqual(store.get('merge:literal'), merge);
},
"when merging into an existing Object value": function (store) {
store.set('merge:object', {
prop1: 2,
prop2: 'prop2',
prop3: {
bazz: 'bazz'
},
prop4: ['foo', 'bar']
});
store.merge('merge:object', merge);

assert.equal(store.get('merge:object:prop1'), 1);
assert.equal(store.get('merge:object:prop2').length, 3);
assert.deepEqual(store.get('merge:object:prop3'), {
foo: 'bar',
bar: 'foo',
bazz: 'bazz'
});
assert.equal(store.get('merge:object:prop4').length, 2);
}
}
}
}).export(module);
64 changes: 63 additions & 1 deletion test/redis-store-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
var vows = require('vows'),
assert = require('assert'),
nconf = require('../lib/nconf'),
data = require('./fixtures/data').data;
data = require('./fixtures/data').data,
merge = require('./fixtures/data').merge;

vows.describe('nconf/stores/redis').addBatch({
"When using the nconf redis store": {
Expand Down Expand Up @@ -147,6 +148,67 @@ vows.describe('nconf/stores/redis').addBatch({
}
}
}
}).addBatch({
"when using the nconf redis store": {
topic: new nconf.stores.Redis(),
"the merge() method": {
"when overriding an existing literal value": {
topic: function (store) {
var that = this;
store.set('merge:literal', 'string-value', function () {
store.merge('merge:literal', merge, function () {
store.get('merge:literal', that.callback);
});
});
},
"should merge correctly": function (err, data) {
assert.deepEqual(data, merge);
}
},
"when overriding an existing Array value": {
topic: function (store) {
var that = this;
store.set('merge:array', [1, 2, 3, 4], function () {
store.merge('merge:array', merge, function () {
store.get('merge:array', that.callback);
});
});
},
"should merge correctly": function (err, data) {
assert.deepEqual(data, merge);
}
},
"when merging into an existing Object value": {
topic: function (store) {
var that = this, current;
current = {
prop1: 2,
prop2: 'prop2',
prop3: {
bazz: 'bazz'
},
prop4: ['foo', 'bar']
};

store.set('merge:object', current, function () {
store.merge('merge:object', merge, function () {
store.get('merge:object', that.callback);
});
});
},
"should merge correctly": function (err, data) {
assert.equal(data['prop1'], 1);
assert.equal(data['prop2'].length, 3);
assert.deepEqual(data['prop3'], {
foo: 'bar',
bar: 'foo',
bazz: 'bazz'
});
assert.equal(data['prop4'].length, 2);
}
}
}
}
}).addBatch({
"When using the nconf redis store": {
topic: new nconf.stores.Redis(),
Expand Down

0 comments on commit 4459ba5

Please sign in to comment.