diff --git a/benchmarks/commonUtilities.js b/benchmarks/commonUtilities.js index ae384b4..3b3d08e 100644 --- a/benchmarks/commonUtilities.js +++ b/benchmarks/commonUtilities.js @@ -124,7 +124,7 @@ module.exports.findDocs = function (d, n, profiler, cb) { function runFrom(i) { if (i === n) { // Finished - console.log("===== RESULT (find with in selector) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); + console.log("===== RESULT (find) ===== " + Math.floor(1000* n / profiler.elapsedSinceLastStep()) + " ops/s"); profiler.step('Finished finding ' + n + ' docs'); return cb(); } diff --git a/lib/model.js b/lib/model.js index 7026845..a8bfaa0 100644 --- a/lib/model.js +++ b/lib/model.js @@ -435,18 +435,28 @@ function modify (obj, updateQuery) { * @param {String} field */ function getDotValue (obj, field) { - var fieldParts = typeof field === 'string' ? field.split('.') : field; + var fieldParts = typeof field === 'string' ? field.split('.') : field + , i, objs; if (!obj) { return undefined; } // field cannot be empty so that means we should return undefined so that nothing can match - if (fieldParts.length === 1) { - return obj[fieldParts[0]]; - } else if (util.isArray(obj[fieldParts[0]])) { - var objs = new Array(); - for (var i = 0; i < obj[fieldParts[0]].length; i += 1) { - objs = objs.concat(getDotValue(obj[fieldParts[0]][i], fieldParts.slice(1).join('.'))); - } - return objs; + if (fieldParts.length === 0) { return obj; } + + if (fieldParts.length === 1) { return obj[fieldParts[0]]; } + + if (util.isArray(obj[fieldParts[0]])) { + // If the next field is an integer, return only this item of the array + i = parseInt(fieldParts[1], 10); + if (typeof i === 'number' && !isNaN(i)) { + return getDotValue(obj[fieldParts[0]][i], fieldParts.slice(2)) + } + + // Return the array of values + objs = new Array(); + for (i = 0; i < obj[fieldParts[0]].length; i += 1) { + objs.push(getDotValue(obj[fieldParts[0]][i], fieldParts.slice(1))); + } + return objs; } else { return getDotValue(obj[fieldParts[0]], fieldParts.slice(1)); } diff --git a/test/model.test.js b/test/model.test.js index d9e6972..4c5f87d 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -840,6 +840,43 @@ describe('Model', function () { assert.isUndefined(model.getDotValue({ hello: 'world' }, 'helloo')); assert.isUndefined(model.getDotValue({ hello: 'world', type: { planet: true } }, 'type.plane')); }); + + it("Can navigate inside arrays with dot notation, and return the array of values in that case", function () { + var dv; + + // Simple array of subdocuments + dv = model.getDotValue({ planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] }, 'planets.name'); + assert.deepEqual(dv, ['Earth', 'Mars', 'Pluton']); + + // Nested array of subdocuments + dv = model.getDotValue({ nedb: true, data: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }, 'data.planets.number'); + assert.deepEqual(dv, [3, 2, 9]); + + // Nested array in a subdocument of an array (yay, inception!) + // TODO: make sure MongoDB doesn't flatten the array (it wouldn't make sense) + dv = model.getDotValue({ nedb: true, data: { planets: [ { name: 'Earth', numbers: [ 1, 3 ] }, { name: 'Mars', numbers: [ 7 ] }, { name: 'Pluton', numbers: [ 9, 5, 1 ] } ] } }, 'data.planets.numbers'); + assert.deepEqual(dv, [[ 1, 3 ], [ 7 ], [ 9, 5, 1 ]]); + }); + + it("Can get a single value out of an array using its index", function () { + var dv; + + // Simple index in dot notation + dv = model.getDotValue({ planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] }, 'planets.1'); + assert.deepEqual(dv, { name: 'Mars', number: 2 }); + + // Out of bounds index + dv = model.getDotValue({ planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] }, 'planets.3'); + assert.isUndefined(dv); + + // Index in nested array + dv = model.getDotValue({ nedb: true, data: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }, 'data.planets.2'); + assert.deepEqual(dv, { name: 'Pluton', number: 9 }); + + // Dot notation with index in the middle + dv = model.getDotValue({ nedb: true, data: { planets: [ { name: 'Earth', number: 3 }, { name: 'Mars', number: 2 }, { name: 'Pluton', number: 9 } ] } }, 'data.planets.0.name'); + dv.should.equal('Earth'); + }); }); @@ -1059,8 +1096,8 @@ describe('Model', function () { model.match({ childrens: [] }, { "childrens": { $size: 3 } }).should.equal(false); }); - it.only('Should throw an error if a query operator is used without being applied to an array and comparing to an integer', function () { - model.match({ a: 5 }, { a: { $size: 1 } }); + it('Should throw an error if a query operator is used without being applied to an array and comparing to an integer', function () { + // model.match({ a: 5 }, { a: { $size: 1 } }); (function () { model.match({ a: [1, 5] }, { a: { $size: 1.4 } }); }).should.throw(); (function () { model.match({ a: [1, 5] }, { a: { $size: 'fdf' } }); }).should.throw(); });