From db03f574a3f77ca73c3f74a6b025cc2d8efdd2b6 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 27 Sep 2013 14:46:10 -0700 Subject: [PATCH] Implement Collection.upsert(). Basically the same code as Collection.update(... { upsert: true }) except that it returns an object with numberAffected and insertedId keys. remove() is back to returning just the number affected, not an object, so that both update() and remove() match the mongo api. Untested. --- packages/minimongo/minimongo.js | 2 +- packages/mongo-livedata/collection.js | 26 ++++++++++++++++++++++--- packages/mongo-livedata/mongo_driver.js | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/minimongo/minimongo.js b/packages/minimongo/minimongo.js index 59cd1be837..95941a7fdc 100644 --- a/packages/minimongo/minimongo.js +++ b/packages/minimongo/minimongo.js @@ -537,7 +537,7 @@ LocalCollection.prototype.remove = function (selector, callback) { LocalCollection._recomputeResults(query); }); self._observeQueue.drain(); - var result = { numberAffected: remove.length }; + var result = remove.length; if (callback) Meteor.defer(function () { callback(null, result); diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index ee6b36a434..77ea20637f 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -337,13 +337,24 @@ var throwIfSelectorIsNotId = function (selector, methodName) { // generating their result until the database has acknowledged // them. In the future maybe we should provide a flag to turn this // off. -_.each(["insert", "update", "remove"], function (name) { +_.each(["insert", "update", "remove", "upsert"], function (name) { Meteor.Collection.prototype[name] = function (/* arguments */) { var self = this; var args = _.toArray(arguments); var callback; var ret; + // Calling `Collection.upsert()` is just like calling `Collection.update()` + // with upsert: true, except that we return the whole object with + // `numberAffected` and `idInserted` keys. So we do the same thing as an + // update, except that we save `isUpsert` to determine what to return when + // we're done. + var isUpsert = false; + if (name === "upsert") { + isUpsert = true; + name = "update"; + } + if (args.length && args[args.length - 1] instanceof Function) callback = args.pop(); @@ -364,7 +375,9 @@ _.each(["insert", "update", "remove"], function (name) { args[0] = Meteor.Collection._rewriteSelector(args[0]); if (name === "update") { - var options = args[2]; + var options = _.clone(args[2]); + if (isUpsert) + options.upsert = true; if (options && options.upsert) { // set `insertedId` if absent. `insertedId` is a Meteor extension. if (options.insertedId) { @@ -427,8 +440,15 @@ _.each(["insert", "update", "remove"], function (name) { // On updates and removes, return whatever the collection returned; on // inserts, always return the id that we generated. If the user provided // a callback, then we expect queryRet to be undefined. - if (name !== "insert") + if (name !== "insert") { ret = queryRet; + // Upsert updates return an object with the number affected and the + // inserted id, but for update queries we only return the number + // affected to match the mongo api. Meteor.Collection.upsert() can be + // used to return the whole object. + if (name === "update" && ! isUpsert) + ret = ret.numberAffected; + } } catch (e) { if (callback) { callback(e); diff --git a/packages/mongo-livedata/mongo_driver.js b/packages/mongo-livedata/mongo_driver.js index 8171ae0d17..149a0fc1bc 100644 --- a/packages/mongo-livedata/mongo_driver.js +++ b/packages/mongo-livedata/mongo_driver.js @@ -289,7 +289,7 @@ MongoConnection.prototype._remove = function (collection_name, selector, try { var collection = self._getCollection(collection_name); collection.remove(replaceTypes(selector, replaceMeteorAtomWithMongo), - {safe: true}, numberAffectedCallback(callback)); + {safe: true}, callback); } catch (e) { write.committed(); throw e;