[api] Added `.merge()` to stores.Memory and stores.Redis

master
indexzero 2011-06-05 01:29:14 -04:00
parent a4f00be991
commit 4459ba54a1
5 changed files with 225 additions and 20 deletions

View File

@ -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;
@ -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.

View File

@ -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);
}
@ -159,6 +147,7 @@ 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 {
@ -166,13 +155,57 @@ Redis.prototype.set = function (key, value, callback) {
// 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
@ -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

11
test/fixtures/data.js vendored
View File

@ -17,4 +17,13 @@ exports.data = {
password: 'password'
}
}
}
};
exports.merge = {
prop1: 1,
prop2: [1, 2, 3],
prop3: {
foo: 'bar',
bar: 'foo'
}
};

View File

@ -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(),
@ -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);

View File

@ -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": {
@ -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(),