diff --git a/lib/types/array.js b/lib/types/array.js index 294c9894059..3d4abf600b9 100644 --- a/lib/types/array.js +++ b/lib/types/array.js @@ -344,13 +344,25 @@ MongooseArray.prototype.shift = function () { }; /** - * Removes items from an array atomically + * Removes items from an array atomically. * * ####Examples: * * doc.array.remove(ObjectId) + * doc.array.remove({ _id: 'someId' }) + * doc.array.remove(36) * doc.array.remove('tag 1', 'tag 2') * + * To remove a document from a subdocument array we may pass an object with a matching `_id`. + * + * doc.subdocs.push({ _id: 4815162342 }) + * doc.subdocs.remove({ _id: 4815162342 }) // removed + * + * Or we may passing the value directly and let mongoose take care of it. + * + * doc.subdocs.push({ _id: 4815162342 }) + * doc.subdocs.remove(4815162342); // works + * * @param {Object} [args...] values to remove * @see mongodb http://www.mongodb.org/display/DOCS/Updating/#Updating-%24pull * @api public diff --git a/lib/types/documentarray.js b/lib/types/documentarray.js index 09e6be1ceeb..570019ec18c 100644 --- a/lib/types/documentarray.js +++ b/lib/types/documentarray.js @@ -7,6 +7,7 @@ var MongooseArray = require('./array') , driver = global.MONGOOSE_DRIVER_PATH || '../drivers/node-mongodb-native' , ObjectId = require(driver + '/objectid') , ObjectIdSchema = require('../schema/objectid') + , utils = require('../utils') , util = require('util') /** @@ -56,8 +57,17 @@ MongooseDocumentArray.prototype.__proto__ = MongooseArray.prototype; */ MongooseDocumentArray.prototype._cast = function (value) { - if (value instanceof this._schema.casterConstructor) + if (value instanceof this._schema.casterConstructor) { return value; + } + + // handle cast('string') or cast(ObjectId) etc. + // only objects are permitted so we can safely assume that + // non-objects are to be interpreted as _id + if (Buffer.isBuffer(value) || + value instanceof ObjectId || !utils.isObject(value)) { + value = { _id: value }; + } return new this._schema.casterConstructor(value, this); }; diff --git a/test/types.array.test.js b/test/types.array.test.js index 392b5539174..63670611367 100644 --- a/test/types.array.test.js +++ b/test/types.array.test.js @@ -1373,24 +1373,42 @@ describe('types array', function(){ }) describe('removing from an array atomically using MongooseArray#remove', function(){ - it('works', function(done){ - var db = start() - , BlogPost = db.model('BlogPost', collection); + var db; + var B; + + before(function(done){ + var schema = Schema({ + numbers: ['number'] + , numberIds: [{ _id: 'number', name: 'string' }] + , stringIds: [{ _id: 'string', name: 'string' }] + , bufferIds: [{ _id: 'buffer', name: 'string' }] + , oidIds: [{ name: 'string' }] + }) + + db = start(); + B = db.model('BlogPost', schema); + done(); + }) - var post = new BlogPost(); + after(function(done){ + db.close(done); + }) + + it('works', function(done){ + var post = new B; post.numbers.push(1, 2, 3); post.save(function (err) { assert.ifError(err); - BlogPost.findById(post._id, function (err, doc) { + B.findById(post._id, function (err, doc) { assert.ifError(err); doc.numbers.remove('1'); doc.save(function (err) { assert.ifError(err); - BlogPost.findById(post.get('_id'), function (err, doc) { + B.findById(post.get('_id'), function (err, doc) { assert.ifError(err); assert.equal(doc.numbers.length, 2); @@ -1399,8 +1417,7 @@ describe('types array', function(){ doc.save(function (err) { assert.ifError(err); - BlogPost.findById(post._id, function (err, doc) { - db.close(); + B.findById(post._id, function (err, doc) { assert.ifError(err); assert.equal(0, doc.numbers.length); done(); @@ -1411,6 +1428,97 @@ describe('types array', function(){ }); }); }) + + describe('with subdocs', function(){ + function docs (arr) { + return arr.map(function (val) { + return { _id: val } + }); + } + + it('supports passing strings', function(done){ + var post = new B({ stringIds: docs('a b c d'.split(' ')) }) + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + post.stringIds.remove('b'); + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + assert.equal(3, post.stringIds.length); + assert.ok(!post.stringIds.id('b')); + done(); + }) + }) + }) + }) + }) + it('supports passing numbers', function(done){ + var post = new B({ numberIds: docs([1,2,3,4]) }) + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + post.numberIds.remove(2,4); + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + assert.equal(2, post.numberIds.length); + assert.ok(!post.numberIds.id(2)); + assert.ok(!post.numberIds.id(4)); + done(); + }) + }) + }) + }) + }) + it('supports passing objectids', function(done){ + var OID = mongoose.Types.ObjectId; + var a = new OID; + var b = new OID; + var c = new OID; + var post = new B({ oidIds: docs([a,b,c]) }) + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + post.oidIds.remove(a,c); + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + assert.equal(1, post.oidIds.length); + assert.ok(!post.oidIds.id(a)); + assert.ok(!post.oidIds.id(c)); + done(); + }) + }) + }) + }) + }) + it('supports passing buffers', function(done){ + var post = new B({ bufferIds: docs(['a','b','c','d']) }) + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + post.bufferIds.remove(new Buffer('a')); + post.save(function (err) { + assert.ifError(err); + B.findById(post, function (err, post) { + assert.ifError(err); + assert.equal(3, post.bufferIds.length); + assert.ok(!post.bufferIds.id(new Buffer('a'))); + done(); + }) + }) + }) + }) + }) + }) }) })