diff --git a/lib/include.js b/lib/include.js index c27da09f..f3ce0d9c 100644 --- a/lib/include.js +++ b/lib/include.js @@ -495,13 +495,9 @@ Inclusion.include = function (objects, include, options, cb) { function includeHasManySimple(callback) { //Map for Indexing objects by their id for faster retrieval var objIdMap2 = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, relation.keyFrom); - console.log("### objIdMap2: " + JSON.stringify(objIdMap2)); - // all ids of primary objects to use in query are original. So, ObjectIds stay ObjectIds and Ints stay Ints. - // No .toString() conversion. - var sourceIds = objIdMap2.originalKeys; filter.where[relation.keyTo] = { - inq: uniq(sourceIds) + inq: uniq(objIdMap2.getKeys()) }; relation.applyScope(null, filter); @@ -511,8 +507,8 @@ Inclusion.include = function (objects, include, options, cb) { if(err) { return callback(err); } - var targetsIdMap = includeUtils.buildOneToManyIdentityMap(targets, relation.keyTo); - includeUtils.join(objIdMap2.simplified, targetsIdMap, function(obj1, valueToMergeIn){ + var targetsIdMap = includeUtils.buildOneToManyIdentityMapWithOrigKeys(targets, relation.keyTo); + includeUtils.join(objIdMap2, targetsIdMap, function(obj1, valueToMergeIn){ defineCachedRelations(obj1); obj1.__cachedRelations[relationName] = valueToMergeIn; processTargetObj(obj1, function(){}); diff --git a/lib/include_utils.js b/lib/include_utils.js index 99919cd2..e9eb286d 100644 --- a/lib/include_utils.js +++ b/lib/include_utils.js @@ -1,7 +1,9 @@ module.exports.buildOneToOneIdentityMap = buildOneToOneIdentityMap; module.exports.buildOneToManyIdentityMap = buildOneToManyIdentityMap; module.exports.buildOneToOneIdentityMapWithOrigKeys = buildOneToOneIdentityMapWithOrigKeys; +module.exports.buildOneToManyIdentityMapWithOrigKeys = buildOneToManyIdentityMapWithOrigKeys; module.exports.join = join; +module.exports.KVMap = KVMap; /** * Effectively builds associative map on id -> object relation. * Map returned in form of object with ids in keys and object as values. @@ -20,72 +22,22 @@ function buildOneToOneIdentityMap(objs, idName) { return idMap; } -/** - * Builds key -> value map on js object base. As js object keys can be only strings keys are stored on value side. - * So, each value should be an object like that: - * { origKey: 34, value: {...}} - * origKey field name should be passed as parameter to function. - * - * @param origKeyField filed name on value side to pick original key from. - * @returns empty object to be filled with key-value pair and additional methods `keys` and `originalKeys` - */ -function newIdMap(origKeyField, valueField) { - //var idMap = Object.create(null); // not any single properties within our identity map - var idMap = {}; - Object.defineProperty(idMap, "set", { - value: function(origKey, value){ - var key = origKey.toString(); - this[key] = {}; - this[key][origKeyField] = origKey; - this[key][valueField] = value; - } - }); - Object.defineProperty(idMap, "keys", { // can ask for keys simply by idMap.keys - get: function(){ return Object.keys(this); }, - enumerable: false // explicitly non-enumerable - }); - Object.defineProperty(idMap, "originalKeys", { // can ask for all original keys by idMap.originalKeys - get: function(){ - var keys = this.keys; - var origKeys = []; - for(var i = 0; i < keys.length; i++) { - var origKey = this[keys[i]][origKeyField]; - origKeys.push(origKey); - } - return origKeys; - }, - enumerable: false // explicitly non-enumerable - }); - Object.defineProperty(idMap, "simplified",{ - get: function(){ - var keys = this.keys; - var simplified = {}; - for(var i = 0; i < keys.length; i++) { - var key = keys[i]; - simplified[key] = this[key][valueField]; - } - return simplified; - } - }); - return idMap; -} /** * Effectively builds associative map on id -> object relation and stores original keys. * Map returned in form of object with ids in keys and object as values. * @param objs array of objects to build from * @param idName name of property to be used as id. Such property considered to be unique across array. * In case of collisions last wins. For non-unique ids use buildOneToManyIdentityMap() - * @returns {{}} object where keys are ids and values are objects itself + * @returns {} object where keys are ids and values are objects itself */ function buildOneToOneIdentityMapWithOrigKeys(objs, idName) { - var idMap = newIdMap("originalKey", "value"); - + var kvMap = new KVMap(); for(var i = 0; i < objs.length; i++) { var obj = objs[i]; var id = obj[idName]; - idMap.set(id, obj); + kvMap.set(id, obj); } - return idMap; + return kvMap; } /** @@ -109,18 +61,15 @@ function buildOneToManyIdentityMap(objs, idName) { } function buildOneToManyIdentityMapWithOrigKeys(objs, idName) { - var idMap = newIdMap("originalKey", "value"); + var kvMap = new KVMap(); for(var i = 0; i < objs.length; i++) { var obj = objs[i]; var id = obj[idName]; - var idString = id.toString(); - if(idString in idMap) { - idMap[idString]["value"].push(obj); - } else { - idMap.set(id, [obj]); - } + var value = kvMap.get(id) || []; + value.push(obj); + kvMap.set(id, value); } - return idMap; + return kvMap; } @@ -132,11 +81,54 @@ function buildOneToManyIdentityMapWithOrigKeys(objs, idName) { * @param mergeF function(obj, objectsToMergeIn) */ function join(oneToOneIdMap, oneToManyIdMap, mergeF) { - var ids = Object.keys(oneToOneIdMap); + var ids = oneToOneIdMap.getKeys(); for(var i = 0; i < ids.length; i++) { var id = ids[i]; - var obj = oneToOneIdMap[id]; - var objectsToMergeIn = oneToManyIdMap[id] || []; + var obj = oneToOneIdMap.get(id); + var objectsToMergeIn = oneToManyIdMap.get(id) || []; mergeF(obj, objectsToMergeIn); } } + + + +function KVMap(){ + var _originalKeyFieldName = 'originalKey'; + var _valueKeyFieldName = 'value'; + var _dict = {}; + var keyToString = function(key){ return key.toString() }; + var mapImpl = { + set: function(key, value){ + var recordObj = {}; + recordObj[_originalKeyFieldName] = key; + recordObj[_valueKeyFieldName] = value; + _dict[keyToString(key)] = recordObj; + return true; + }, + get: function(key){ + var storeObj = _dict[keyToString(key)]; + if(storeObj) { + return storeObj[_valueKeyFieldName]; + } else { + return undefined; + } + }, + remove: function(key){ + delete _dict[keyToString(key)]; + return true; + }, + exist: function(key) { + var result = _dict.hasOwnProperty(keyToString(key)); + return result; + }, + getKeys: function(){ + var result = []; + for(var key in _dict) { + result.push(_dict[key][_originalKeyFieldName]); + } + return result; + } + + }; + return mapImpl; +} diff --git a/test/include_util.test.js b/test/include_util.test.js index adbeffe2..3cbe6fa9 100644 --- a/test/include_util.test.js +++ b/test/include_util.test.js @@ -31,56 +31,19 @@ describe('include_util', function(){ result["33"]["letter"].should.equal("C"); }); }); - describe('#buildOneToOneIdentityMapWithOrigKeys', function(){ it('should return an object with keys', function(){ var objs = [ {id: 11, letter: "A"}, {id: 22, letter: "B"} ]; - var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id"); - result.should.be.an.instanceOf(Object); - result.should.have.property("11"); - result.should.have.property("22"); - Object.keys(result).should.have.lengthOf(2); // no additional properties - }); - it('should return all stringized keys with .keys method', function(){ - var objs = [ - {id: 11, letter: "A"}, - {id: 22, letter: "B"}, - {id: "cc", letter: "C"} - ]; - var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id"); - var keys = result.keys; - keys.should.be.instanceOf(Array); - keys.should.have.lengthOf(3); - keys.should.be.eql(['11', '22', 'cc']); - }); - it("should return all original keys with .originalKeys method", function(){ - var objs = [ - {id: 11, letter: "A"}, - {id: 22, letter: "B"}, - {id: "vv", letter: "V"} - ]; - var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id"); - var origKeys = result.originalKeys; - origKeys.should.be.instanceOf(Array); - origKeys.should.have.lengthOf(3); - origKeys.should.be.eql([11, 22, 'vv']); - }); - it('should have .keys and .originalKeys in same order', function(){ - var objs = [ - {id: 11, letter: "A"}, - {id: 22, letter: "B"}, - {id: "vv", letter: "V"} - ]; - var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, "id"); - var keys = result.keys; - var origKeys = result.originalKeys; - origKeys.map(function(a){return a.toString();}).should.be.eql(keys); + var result = includeUtils.buildOneToOneIdentityMapWithOrigKeys(objs, 'id'); + result.get(11).should.be.ok; + result.get(22).should.be.ok; + result.getKeys().should.have.lengthOf(2); // no additional properties }); }); - describe('#buildOneToManyIdentityMap', function(){ + describe('#buildOneToManyIdentityMap', function(){ it('should return an object with keys', function(){ var objs = [ {id: 11, letter: "A"}, @@ -108,3 +71,63 @@ describe('include_util', function(){ }); }); }); + + +describe('KVMap', function(){ + it('should allow to set and get value with key string', function(){ + var map = new includeUtils.KVMap(); + map.set('name', 'Alex'); + map.set('gender', true); + map.set('age', 25); + map.get('name').should.be.equal('Alex'); + map.get('gender').should.be.equal(true); + map.get('age').should.be.equal(25); + }); + it('should allow to set and get value with arbitrary key type', function(){ + var map = new includeUtils.KVMap(); + map.set('name', 'Alex'); + map.set(true, 'male'); + map.set(false, false); + map.set({isTrue: 'yes'}, 25); + map.get('name').should.be.equal('Alex'); + map.get(true).should.be.equal('male'); + map.get(false).should.be.equal(false); + map.get({isTrue: 'yes'}).should.be.equal(25); + }); + it('should not allow to get values with [] operator', function(){ + var map = new includeUtils.KVMap(); + map.set('name', 'Alex'); + (map['name'] === undefined).should.be.equal(true); + }); + it('should provide .exist() method for checking if key presented', function(){ + var map = new includeUtils.KVMap(); + map.set('one', 1); + map.set(2, 'two'); + map.set(true, 'true'); + map.exist('one').should.be.true; + map.exist(2).should.be.true; + map.exist(true).should.be.true; + map.exist('two').should.be.false; + }); + it('should return array of original keys with .getKeys()', function(){ + var map = new includeUtils.KVMap(); + map.set('one', 1); + map.set(2, 'two'); + map.set(true, 'true'); + var keys = map.getKeys(); + keys.should.containEql('one'); + keys.should.containEql(2); + keys.should.containEql(true); + }); + it('should allow to store and fetch arrays', function(){ + var map = new includeUtils.KVMap(); + map.set(1, [1, 2, 3]); + map.set(2, [2, 3, 4]); + var valueOne = map.get(1); + valueOne.should.be.eql([1, 2, 3]); + valueOne.push(99); + map.set(1, valueOne); + var valueOneUpdated = map.get(1); + valueOneUpdated.should.be.eql([1, 2, 3, 99]); + }); +});