From 986ffbae49c12d1aa2b9dc85699fc4b792e3352d Mon Sep 17 00:00:00 2001 From: matt debergalis Date: Wed, 9 May 2012 11:38:26 -0700 Subject: [PATCH] Provide document ID to Meteor.Collection callback. --- docs/client/api.html | 13 +++-- docs/client/api.js | 6 +-- packages/mongo-livedata/collection.js | 51 ++++++++++++------- .../mongo-livedata/mongo_livedata_tests.js | 2 +- 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 466555e6a0..1a15d335c6 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -510,15 +510,18 @@ in the database, and return the ID. On the server, if you don't provide a callback, then `insert` blocks until the database acknowledges the write, or throws an exception if -something went wrong. If you do provide a callback, `insert` returns -immediately. Once the insert completes (or fails), the callback is -called with error and result arguments, same as for -[`methods`](#methods_header). +something went wrong. If you do provide a callback, `insert` still +returns the ID immediately. Once the insert completes (or fails), the +callback is called with error and result arguments. In an error case, +`result` is undefined. If the insert is successful, `error` is +undefined and `result` is the new document ID. On the client, `insert` never blocks. If you do not provide a callback and the insert fails on the server, then Meteor will log a warning to the console. If you provide a callback, Meteor will call that function -with the error or result of the server's insert. +with `error` and `result` arguments. In an error case, `result` is +undefined. If the insert is successful, `error` is undefined and +`result` is the new document ID. Example: diff --git a/docs/client/api.js b/docs/client/api.js index ad111fedf9..30d16f47c5 100644 --- a/docs/client/api.js +++ b/docs/client/api.js @@ -395,7 +395,7 @@ Template.api.insert = { descr: "The document to insert. Should not yet have an _id attribute."}, {name: "callback", type: "Function", - descr: "Optional. If present, called with an error object as the first argument and the _id as the second."} + descr: "Optional. If present, called with an error object as the first argument and, if no error, the _id as the second."} ] }; @@ -415,7 +415,7 @@ Template.api.update = { descr: "Specifies how to modify the documents"}, {name: "callback", type: "Function", - descr: "Optional. If present, called with an error object as the first argument and the result as the second."} + descr: "Optional. If present, called with an error object as its argument."} ], options: [ {name: "multi", @@ -436,7 +436,7 @@ Template.api.remove = { descr: "Specifies which documents to remove"}, {name: "callback", type: "Function", - descr: "Optional. If present, called with an error object as the first argument and the result as the second."} + descr: "Optional. If present, called with an error object as its argument."} ] }; diff --git a/packages/mongo-livedata/collection.js b/packages/mongo-livedata/collection.js index 3c971a46d4..23e8b3e3bc 100644 --- a/packages/mongo-livedata/collection.js +++ b/packages/mongo-livedata/collection.js @@ -92,19 +92,19 @@ Meteor.Collection = function (name, manager, driver) { self._prefix = '/' + name + '/'; m[self._prefix + 'insert'] = function (/* selector, options */) { self._maybe_snapshot(); - // Allow exceptions to propagate + // insert returns nothing. allow exceptions to propagate. self._collection.insert.apply(self._collection, _.toArray(arguments)); }; m[self._prefix + 'update'] = function (/* selector, mutator, options */) { self._maybe_snapshot(); - // Allow exceptions to propagate + // update returns nothing. allow exceptions to propagate. self._collection.update.apply(self._collection, _.toArray(arguments)); }; m[self._prefix + 'remove'] = function (/* selector */) { self._maybe_snapshot(); - // Allow exceptions to propagate + // remove returns nothing. allow exceptions to propagate. self._collection.remove.apply(self._collection, _.toArray(arguments)); }; @@ -151,8 +151,10 @@ _.extend(Meteor.Collection.prototype, { // provided, they block until the operation is complete, and throw an // exception if it fails; if a callback is provided, then they don't // necessarily block, and they call the callback when they finish with -// zero arguments on success, or one argument, an exception, on -// failure; on the client, blocking is impossible, so if a callback +// error and result arguments. (The insert method provides the +// document ID as its result; update and remove don't provide a result.) +// +// On the client, blocking is impossible, so if a callback // isn't provided, they just return immediately and any error // information is lost. // @@ -169,9 +171,11 @@ _.each(["insert", "update", "remove"], function (name) { Meteor.Collection.prototype[name] = function (/* arguments */) { var self = this; var args = _.toArray(arguments); + var callback; + var ret; if (args.length && args[args.length - 1] instanceof Function) - var callback = args.pop(); + callback = args.pop(); if (Meteor.is_client && !callback) // Client can't block, so it can't report errors by exception, @@ -191,33 +195,42 @@ _.each(["insert", "update", "remove"], function (name) { args[0] = _.extend({}, args[0]); if ('_id' in args[0]) throw new Error("Do not pass an _id to insert. Meteor will generate the _id for you."); - var ret = args[0]._id = Meteor.uuid(); + ret = args[0]._id = Meteor.uuid(); } if (self._manager && self._manager !== Meteor.default_server) { - // NB: on failure, allow exception to propagate - self._manager.apply(self._prefix + name, args, callback); - } - else { + // just remote to another endpoint, propagate return value or + // exception. + if (callback) + // asynchronous: on success, callback should return ret + // (document ID for insert, undefined for update and + // remove), not the method's result. + self._manager.apply(self._prefix + name, args, function (error, result) { + callback(error, !error && ret); + }); + else + // synchronous: propagate exception + self._manager.apply(self._prefix + name, args); + + } else { + // it's my collection. descend into the collection object + // and propagate any exception. try { self._collection[name].apply(self._collection, args); } catch (e) { if (callback) { callback(e); - return; + return null; } - - // Note that on the client, this will never happen, because - // we will have been provided with a default callback. (This - // is nice because it matches the behavior of named - // collections, which on the client never throw exceptions - // directly.) throw e; } - callback && callback(); + // on success, return *ret*, not the manager's return value. + callback && callback(null, ret); } + // both sync and async, unless we threw an exception, return ret + // (new document ID for insert, undefined otherwise). return ret; }; }); diff --git a/packages/mongo-livedata/mongo_livedata_tests.js b/packages/mongo-livedata/mongo_livedata_tests.js index 8975905716..3e925fb697 100644 --- a/packages/mongo-livedata/mongo_livedata_tests.js +++ b/packages/mongo-livedata/mongo_livedata_tests.js @@ -9,7 +9,7 @@ testAsyncMulti("mongo-livedata - database failure reporting", [ function (test, expect) { var ftc = Meteor._FailureTestCollection; - var exception = function (err) { + var exception = function (err, res) { test.instanceOf(err, Error); };