diff --git a/README.md b/README.md index ccf3891..4fa9fc9 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ db.count({}, function (err, count) { * `query` is the same kind of finding query you use with `find` and `findOne` * `update` specifies how the documents should be modified. It is either a new document or a set of modifiers (you cannot use both together, it doesn't make sense!) * A new document will replace the matched docs - * The modifiers create the fields they need to modify if they don't exist, and you can apply them to subdocs. Available field modifiers are `$set` to change a field's value, `$unset` to delete a field and `$inc` to increment a field's value. To work on arrays, you have `$push`, `$pop`, `$addToSet`, and the special `$each`. See examples below for the syntax. + * The modifiers create the fields they need to modify if they don't exist, and you can apply them to subdocs. Available field modifiers are `$set` to change a field's value, `$unset` to delete a field and `$inc` to increment a field's value. To work on arrays, you have `$push`, `$pop`, `$addToSet`, `$pull`, and the special `$each`. See examples below for the syntax. * `options` is an object with two possible parameters * `multi` (defaults to `false`) which allows the modification of several documents if set to true * `upsert` (defaults to `false`) if you want to insert a new document corresponding to the `update` rules if your `query` doesn't match anything @@ -350,6 +350,12 @@ 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 +db.update({ _id: 'id6' }, { $pull: { fruits: 'apple' } }, {}, function () { + // Now the fruits array is ['orange', 'pear'] +}); + // $each can be used to $push or $addToSet multiple values at once // This example works the same way with $addToSet db.update({ _id: 'id6' }, { $push: { fruits: ['banana', 'orange'] } }, {}, function () { diff --git a/lib/model.js b/lib/model.js index 257e8de..082d8ee 100644 --- a/lib/model.js +++ b/lib/model.js @@ -306,6 +306,22 @@ lastStepModifierFunctions.$pop = function (obj, field, value) { }; +/** + * Removes all instances of a value from an existing array + */ +lastStepModifierFunctions.$pull = function (obj, field, value) { + var arr, i; + + if (!util.isArray(obj[field])) { throw "Can't $pull an element from non-array values"; } + + arr = obj[field]; + for (i = arr.length - 1; i >= 0; i -= 1) { + if (_.isEqual(arr[i], value)) { + arr.splice(i, 1); + } + } +}; + /** * Increment a numeric field's value */ diff --git a/test/model.test.js b/test/model.test.js index c503b53..b42da0c 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -541,6 +541,51 @@ describe('Model', function () { }); // End of '$pop modifier' + describe('$pull modifier', function () { + + it('Can remove an element from a set', function () { + var obj = { arr: ['hello', 'world'] } + , modified; + + modified = model.modify(obj, { $pull: { arr: 'world' } }); + assert.deepEqual(modified, { arr: ['hello'] }); + + obj = { arr: ['hello'] }; + modified = model.modify(obj, { $pull: { arr: 'world' } }); + assert.deepEqual(modified, { arr: ['hello'] }); + }); + + it('Can remove multiple matching elements', function () { + var obj = { arr: ['hello', 'world', 'hello', 'world'] } + , modified; + + modified = model.modify(obj, { $pull: { arr: 'world' } }); + assert.deepEqual(modified, { arr: ['hello', 'hello'] }); + }); + + it('Throw if we try to pull from a non-array', function () { + var obj = { arr: 'hello' } + , modified; + + (function () { + modified = model.modify(obj, { $pull: { arr: 'world' } }); + }).should.throw(); + }); + + it('Use deep-equality to check whether we can remove a value from a set', function () { + var obj = { arr: [{ b: 2 }, { b: 3 }] } + , modified; + + modified = model.modify(obj, { $pull: { arr: { b: 3 } } }); + assert.deepEqual(modified, { arr: [ { b: 2 } ] }); + + obj = { arr: [ { b: 2 } ] } + modified = model.modify(obj, { $pull: { arr: { b: 3 } } }); + assert.deepEqual(modified, { arr: [{ b: 2 }] }); + }); + + }); // End of '$pull modifier' + }); // ==== End of 'Modifying documents' ==== //