[api] Added .merge()
to stores.Memory and stores.Redis
This commit is contained in:
parent
a4f00be991
commit
4459ba54a1
5 changed files with 225 additions and 20 deletions
|
@ -67,7 +67,7 @@ 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] = {};
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
9
test/fixtures/data.js
vendored
9
test/fixtures/data.js
vendored
|
@ -17,4 +17,13 @@ exports.data = {
|
|||
password: 'password'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.merge = {
|
||||
prop1: 1,
|
||||
prop2: [1, 2, 3],
|
||||
prop3: {
|
||||
foo: 'bar',
|
||||
bar: 'foo'
|
||||
}
|
||||
};
|
|
@ -7,7 +7,8 @@
|
|||
|
||||
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": {
|
||||
|
@ -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);
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in a new issue