diff --git a/lib/model.js b/lib/model.js index cb20c09..4c7e36d 100644 --- a/lib/model.js +++ b/lib/model.js @@ -143,7 +143,34 @@ modifierFunctions.$set = function (obj, field, value) { } }; -modifierFunctions.$inc = function () {}; + +/** + * Increase (or decrease) a 'number' field + * Create and initialize it if needed + * @param {Object} obj The model to set a field for + * @param {String} field Can contain dots, in that case that means we will set a subfield recursively + * @param {Model} value + */ +modifierFunctions.$inc = function (obj, field, value) { + var fieldParts = field.split('.'); + + if (typeof value !== 'number') { throw value + " must be a number"; } + + if (fieldParts.length === 1) { + if (typeof obj[field] !== 'number') { + if (!_.has(obj, field)) { + obj[field] = value; + } else { + throw "Don't use the $inc modifier on non-number fields"; + } + } else { + obj[field] += value; + } + } else { + obj[fieldParts[0]] = obj[fieldParts[0]] || {}; + modifierFunctions.$inc(obj[fieldParts[0]], fieldParts.slice(1).join('.'), value); + } +}; /** diff --git a/test/model.test.js b/test/model.test.js index 57d6c02..df9c08b 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -250,7 +250,6 @@ describe('Model', function () { }); describe('$set modifier', function () { - it('Can change already set fields without modfifying the underlying object', function () { var obj = { some: 'thing', yup: 'yes', nay: 'noes' } , updateQuery = { $set: { some: 'changed', nay: 'yes indeed' } } @@ -285,7 +284,42 @@ describe('Model', function () { _.isEqual(modified, { yup: { subfield: 'changed', yop: 'yes indeed' }, totally: { doesnt: { exist: 'now it does' } } }).should.equal(true); }); + }); + + 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 () { + (function () { + var obj = { some: 'thing', yup: 'yes', nay: 2 } + , updateQuery = { $inc: { nay: 'notanumber' } } + , modified = model.modify(obj, updateQuery); + }).should.throw(); + + (function () { + var obj = { some: 'thing', yup: 'yes', nay: 'nope' } + , updateQuery = { $inc: { nay: 1 } } + , modified = model.modify(obj, updateQuery); + }).should.throw(); + }); + it('Can increment number fields or create and initialize them if needed', function () { + var obj = { some: 'thing', nay: 40 } + , modified; + + modified = model.modify(obj, { $inc: { nay: 2 } }); + _.isEqual(modified, { some: 'thing', nay: 42 }).should.equal(true); + + // Incidentally, this tests that obj was not modified + modified = model.modify(obj, { $inc: { inexistent: -6 } }); + _.isEqual(modified, { some: 'thing', nay: 40, inexistent: -6 }).should.equal(true); + }); + + it('Works recursively', function () { + var obj = { some: 'thing', nay: { nope: 40 } } + , modified; + + modified = model.modify(obj, { $inc: { "nay.nope": -2, "blip.blop": 123 } }); + _.isEqual(modified, { some: 'thing', nay: { nope: 38 }, blip: { blop: 123 } }).should.equal(true); + }); }); }); // ==== End of 'Modifying documents' ==== //