diff --git a/README.md b/README.md index 4fa9fc9..1db88fb 100644 --- a/README.md +++ b/README.md @@ -350,11 +350,15 @@ db.update({ _id: 'id6' }, { $addToSet: { fruits: 'apple' } }, {}, function () { // If we had used a fruit not in the array, e.g. 'banana', it would have been added to the array }); -// $pull removes all instances of a value from an existing array -// Equality is deep-checked +// $pull removes all values matching a value or even any NeDB query from the array db.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, function () { // Now the fruits array is ['orange', 'pear'] }); +db.update({ _id: 'id6' }, { $pull: { fruits: $in: ['apple', 'pear'] } }, {}, function () { + // Now the fruits array is ['orange'] +}); + + // $each can be used to $push or $addToSet multiple values at once // This example works the same way with $addToSet diff --git a/lib/model.js b/lib/model.js index f409e15..e336c17 100644 --- a/lib/model.js +++ b/lib/model.js @@ -141,7 +141,7 @@ function isPrimitiveType (obj) { typeof obj === 'string' || obj === null || util.isDate(obj) || - util.isArray(obj)); + util.isArray(obj)); } @@ -619,21 +619,27 @@ logicalOperators.$not = function (obj, query) { * @param {Object} query */ function match (obj, query) { - var queryKeys = Object.keys(query) - , queryKey, queryValue - , i - ; + var queryKeys, queryKey, queryValue, i; + // Primitive query against a primitive type + // This is a bit of a hack since we construct an object with an arbitrary key only to dereference it later + // But I don't have time for a cleaner implementation now + if (isPrimitiveType(obj) || isPrimitiveType(query)) { + return matchQueryPart({ needAKey: obj }, 'needAKey', query); + } + + // Normal query + queryKeys = Object.keys(query); for (i = 0; i < queryKeys.length; i += 1) { - queryKey = queryKeys[i]; - queryValue = query[queryKey]; + queryKey = queryKeys[i]; + queryValue = query[queryKey]; - if (queryKey[0] === '$') { + if (queryKey[0] === '$') { if (!logicalOperators[queryKey]) { throw "Unknown logical operator " + queryKey; } if (!logicalOperators[queryKey](obj, queryValue)) { return false; } - } else { + } else { if (!matchQueryPart(obj, queryKey, queryValue)) { return false; } - } + } } return true; @@ -645,8 +651,7 @@ function match (obj, query) { */ function matchQueryPart (obj, queryKey, queryValue) { var objValue = getDotValue(obj, queryKey) - , i, keys, firstChars, dollarFirstChars - ; + , i, keys, firstChars, dollarFirstChars; // Check if the object value is an array treat it as an array of { obj, query } // Where there needs to be at least one match @@ -695,6 +700,7 @@ module.exports.serialize = serialize; module.exports.deserialize = deserialize; module.exports.deepCopy = deepCopy; module.exports.checkObject = checkObject; +module.exports.isPrimitiveType = isPrimitiveType; module.exports.modify = modify; module.exports.getDotValue = getDotValue; module.exports.match = match; diff --git a/test/model.test.js b/test/model.test.js index 8751b92..a96fe3c 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -165,10 +165,21 @@ describe('Model', function () { model.checkObject(obj); }); - - it.only('Can check if an object is a primitive or not', function () { - - }); + + it('Can check if an object is a primitive or not', function () { + model.isPrimitiveType(5).should.equal(true); + model.isPrimitiveType('sdsfdfs').should.equal(true); + model.isPrimitiveType(0).should.equal(true); + model.isPrimitiveType(true).should.equal(true); + model.isPrimitiveType(false).should.equal(true); + model.isPrimitiveType(new Date()).should.equal(true); + model.isPrimitiveType([]).should.equal(true); + model.isPrimitiveType([3, 'try']).should.equal(true); + model.isPrimitiveType(null).should.equal(true); + + model.isPrimitiveType({}).should.equal(false); + model.isPrimitiveType({ a: 42 }).should.equal(false); + }); }); // ==== End of 'Object checking' ==== // @@ -300,48 +311,48 @@ describe('Model', function () { _.isEqual(modified, { yup: { subfield: 'changed', yop: 'yes indeed' }, totally: { doesnt: { exist: 'now it does' } } }).should.equal(true); }); }); // End of '$set modifier' - - describe('$unset modifier', function () { - - it('Can delete a field, not throwing an error if the field doesnt exist', function () { - var obj, updateQuery, modified; - + + describe('$unset modifier', function () { + + it('Can delete a field, not throwing an error if the field doesnt exist', function () { + var obj, updateQuery, modified; + obj = { yup: 'yes', other: 'also' } updateQuery = { $unset: { yup: true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, { other: 'also' }); + assert.deepEqual(modified, { other: 'also' }); obj = { yup: 'yes', other: 'also' } updateQuery = { $unset: { nope: true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, obj); - + assert.deepEqual(modified, obj); + obj = { yup: 'yes', other: 'also' } updateQuery = { $unset: { nope: true, other: true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, { yup: 'yes' }); - }); - + assert.deepEqual(modified, { yup: 'yes' }); + }); + it('Can unset sub-fields and entire nested documents', function () { - var obj, updateQuery, modified; - + var obj, updateQuery, modified; + obj = { yup: 'yes', nested: { a: 'also', b: 'yeah' } } updateQuery = { $unset: { nested: true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, { yup: 'yes' }); - + assert.deepEqual(modified, { yup: 'yes' }); + obj = { yup: 'yes', nested: { a: 'also', b: 'yeah' } } updateQuery = { $unset: { 'nested.a': true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, { yup: 'yes', nested: { b: 'yeah' } }); - + assert.deepEqual(modified, { yup: 'yes', nested: { b: 'yeah' } }); + obj = { yup: 'yes', nested: { a: 'also', b: 'yeah' } } updateQuery = { $unset: { 'nested.a': true, 'nested.b': true } } modified = model.modify(obj, updateQuery); - assert.deepEqual(modified, { yup: 'yes', nested: {} }); + assert.deepEqual(modified, { yup: 'yes', nested: {} }); }); - - }); // End of '$unset modifier' + + }); // End of '$unset modifier' describe('$inc modifier', function () { it('Throw an error if you try to use it with a non-number or on a non number field', function () { @@ -545,7 +556,7 @@ describe('Model', function () { }); // End of '$pop modifier' - describe.skip('$pull modifier', function () { + describe('$pull modifier', function () { it('Can remove an element from a set', function () { var obj = { arr: ['hello', 'world'] } @@ -587,19 +598,19 @@ describe('Model', function () { modified = model.modify(obj, { $pull: { arr: { b: 3 } } }); assert.deepEqual(modified, { arr: [{ b: 2 }] }); }); - - it('Can use any kind of nedb query with $pull', function () { - var obj = { arr: [4, 7, 12, 2], other: 'yup' } - , modified - ; - -// modified = model.modify(obj, { $pull: { arr: { $gte: 5 } } }); -// assert.deepEqual(modified, { arr: [4, 2], other: 'yup' }); - -// obj = { arr: [{ b: 4 }, { b: 7 }, { b: 1 }], other: 'yeah' }; -// modified = model.modify(obj, { $pull: { arr: { b: { $gte: 5} } } }); -// assert.deepEqual(modified, { arr: [{ b: 4 }, { b: 1 }], other: 'yeah' }); - }); + + it('Can use any kind of nedb query with $pull', function () { + var obj = { arr: [4, 7, 12, 2], other: 'yup' } + , modified + ; + + modified = model.modify(obj, { $pull: { arr: { $gte: 5 } } }); + assert.deepEqual(modified, { arr: [4, 2], other: 'yup' }); + + obj = { arr: [{ b: 4 }, { b: 7 }, { b: 1 }], other: 'yeah' }; + modified = model.modify(obj, { $pull: { arr: { b: { $gte: 5} } } }); + assert.deepEqual(modified, { arr: [{ b: 4 }, { b: 1 }], other: 'yeah' }); + }); }); // End of '$pull modifier'