[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,13 +67,13 @@ Memory.prototype.set = function (key, value) {
|
||||||
//
|
//
|
||||||
while (path.length > 1) {
|
while (path.length > 1) {
|
||||||
key = path.shift();
|
key = path.shift();
|
||||||
if (!target[key]) {
|
if (!target[key] || typeof target[key] !== 'object') {
|
||||||
target[key] = {};
|
target[key] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
target = target[key];
|
target = target[key];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the specified value in the nested JSON structure
|
// Set the specified value in the nested JSON structure
|
||||||
key = path.shift();
|
key = path.shift();
|
||||||
target[key] = value;
|
target[key] = value;
|
||||||
|
@ -112,6 +112,55 @@ Memory.prototype.clear = function (key) {
|
||||||
return true;
|
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)
|
// ### function reset (callback)
|
||||||
// Clears all keys associated with this instance.
|
// 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"
|
// Set the callback if not provided for "fire and forget"
|
||||||
callback = callback || function () { };
|
callback = callback || function () { };
|
||||||
|
|
||||||
function addKey (partial, next) {
|
this._addKeys(key, function (err) {
|
||||||
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) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(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
|
// (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).
|
// member of that Object, the entire Object need not be retrieved).
|
||||||
//
|
//
|
||||||
|
self.cache.set(key, value);
|
||||||
self._setObject(fullKey, value, callback);
|
self._setObject(fullKey, value, callback);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -166,13 +155,57 @@ Redis.prototype.set = function (key, value, callback) {
|
||||||
// If the value is a simple literal (or an `Array`) then JSON
|
// If the value is a simple literal (or an `Array`) then JSON
|
||||||
// stringify it and put it into Redis.
|
// stringify it and put it into Redis.
|
||||||
//
|
//
|
||||||
value = JSON.stringify(value);
|
|
||||||
self.cache.set(key, value);
|
self.cache.set(key, value);
|
||||||
|
value = JSON.stringify(value);
|
||||||
self.redis.set(fullKey, value, callback);
|
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)
|
// ### function clear (key, callback)
|
||||||
// #### @key {string} Key to remove from this instance
|
// #### @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)
|
// ### @private function _setObject (key, value, callback)
|
||||||
// #### @key {string} Key to set in this instance
|
// #### @key {string} Key to set in this instance
|
||||||
|
|
11
test/fixtures/data.js
vendored
11
test/fixtures/data.js
vendored
|
@ -17,4 +17,13 @@ exports.data = {
|
||||||
password: 'password'
|
password: 'password'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
exports.merge = {
|
||||||
|
prop1: 1,
|
||||||
|
prop2: [1, 2, 3],
|
||||||
|
prop3: {
|
||||||
|
foo: 'bar',
|
||||||
|
bar: 'foo'
|
||||||
|
}
|
||||||
|
};
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
var vows = require('vows'),
|
var vows = require('vows'),
|
||||||
assert = require('assert'),
|
assert = require('assert'),
|
||||||
nconf = require('../lib/nconf');
|
nconf = require('../lib/nconf'),
|
||||||
|
merge = require('./fixtures/data').merge;
|
||||||
|
|
||||||
vows.describe('nconf/stores/memory').addBatch({
|
vows.describe('nconf/stores/memory').addBatch({
|
||||||
"When using the nconf memory store": {
|
"When using the nconf memory store": {
|
||||||
topic: new nconf.stores.Memory(),
|
topic: new nconf.stores.Memory(),
|
||||||
|
@ -46,6 +47,38 @@ vows.describe('nconf/stores/memory').addBatch({
|
||||||
assert.isTrue(store.clear('foo:bar:bazz'));
|
assert.isTrue(store.clear('foo:bar:bazz'));
|
||||||
assert.isTrue(typeof store.get('foo:bar:bazz') === 'undefined');
|
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);
|
}).export(module);
|
|
@ -8,7 +8,8 @@
|
||||||
var vows = require('vows'),
|
var vows = require('vows'),
|
||||||
assert = require('assert'),
|
assert = require('assert'),
|
||||||
nconf = require('../lib/nconf'),
|
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({
|
vows.describe('nconf/stores/redis').addBatch({
|
||||||
"When using the nconf redis store": {
|
"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({
|
}).addBatch({
|
||||||
"When using the nconf redis store": {
|
"When using the nconf redis store": {
|
||||||
topic: new nconf.stores.Redis(),
|
topic: new nconf.stores.Redis(),
|
||||||
|
|
Loading…
Reference in a new issue