diff --git a/lib/Instance.js b/lib/Instance.js index a62969d..43a75b1 100644 --- a/lib/Instance.js +++ b/lib/Instance.js @@ -2,6 +2,8 @@ var ObjectID = require('mongodb').ObjectID, _ = require('lodash'), EventEmitter = require('events').EventEmitter, debug = require('debug')('iridium:Instance'), + fn = require('functionality'), + Q = require('q'), inherit = require('./utils/Inherit.js'), diff = require('./utils/diff.js'); @@ -62,114 +64,88 @@ Object.defineProperty(Instance.prototype, 'document', { enumerable: false }); -Instance.prototype.save = function(conditions, changes, callback) { - /// - /// Saves changes made to the current instance to the database without waiting for a response - /// - /// - /// Saves changes made to the current instance to the database without waiting for a response - /// MongoDB changes query to be used instead of differential patching - /// - /// - /// Saves changes made to the current instance to the database - /// A function which is called when the save has been completed - /// - /// - /// Saves changes made to the current instance to the database - /// MongoDB changes query to be used instead of differential patching - /// A function which is called when the save has been completed - /// - /// - /// Saves changes made to the current instance to the database - /// A set of conditions used to determine aspects of the document to update, merged with _id: ... - /// MongoDB changes query to be used instead of differential patching - /// - /// - /// Saves changes made to the current instance to the database - /// A set of conditions used to determine aspects of the document to update, merged with _id: ... - /// MongoDB changes query to be used instead of differential patching - /// A function which is called when the save has been completed - /// - - var args = Array.prototype.splice.call(arguments, 0); - conditions = null; - changes = null; - callback = null; +Instance.prototype.save = fn.first(function() { + this.deferred = Q.defer(); +}).on(fn.opt(Function), function(callback) { + this.conditions = this.context.__state.model.uniqueConditions(this.context.__state.modified); - for(var i = args.length - 1; i >= 0; i--) { - if('function' == typeof args[i]) callback = args[i]; - else if(!changes) changes = args[i]; - else conditions = args[i]; + var validation = this.context.__state.model.schemaValidator.validate(this.context.__state.modified); + if(validation.failed) { + this.aborted = true; + return this.deferred.reject(validation.error); } - conditions = conditions || {}; + var original = _.cloneDeep(this.context.__state.original); + var modified = _.cloneDeep(this.context.__state.modified); - var onError = (function (err) { - this.emit('error', err); - if(callback) return callback(err); - else throw err; - }).bind(this); - - if(this.__state.isNew) { - var toCreate = _.cloneDeep(this.__state.modified); - - this.__state.model.onCreating(toCreate, (function(err) { - if(err) return onError(err); - this.emit('creating', toCreate); + this.context.__state.model.toSource(original); + this.context.__state.model.toSource(modified); - this.__state.model.toSource(toCreate); - this.__state.model.collection.insert(toCreate, { w: 1 }, (function(err, created) { - if(err) return onError(err); + this.changes = Instance.diff(original, modified); - this.__state.isNew = false; - this.__state.isPartial = false; - this.__state.model.onRetrieved(conditions, created[0], callback || function() { }, (function(value) { - this.__state.model.fromSource(value); - this.__state.original = _.cloneDeep(value); - this.__state.modified = _.cloneDeep(value); - this.__extendSchema(); - this.emit('retrieved', this); - return this; - }).bind(this), { partial: this.__state.isPartial }); - }).bind(this)); - }).bind(this)); - } + if(callback) promiseCallback(this.deferred.promise, callback); +}).on(Object, fn.opt(Function), function(changes, callback) { + this.conditions = this.context.__state.model.uniqueConditions(this.context.__state.modified); + this.changes = changes; - if(!changes) { - var validation = this.__state.model.schemaValidator.validate(this.__state.modified); - if(validation.failed) return callback(validation.error); + if(callback) promiseCallback(this.deferred.promise, callback); +}).on(Object, Object, fn.opt(Function), function(conditions, changes, callback) { + this.conditions = conditions; + this.changes = changes; - var original = _.cloneDeep(this.__state.original); - var modified = _.cloneDeep(this.__state.modified); + this.context.__state.model.toSource(this.conditions); + _.merge(this.conditions, this.context.__state.model.uniqueConditions(this.context.__state.modified)); - this.__state.model.toSource(original); - this.__state.model.toSource(modified); + if(callback) promiseCallback(this.deferred.promise, callback); +}).then(function() { + if(this.aborted) return this.deferred.promise; - changes = Instance.diff(original, modified); + if(Object.keys(this.changes).length == 0) { + this.deferred.resolve(this.context); + return this.deferred.promise; } - if(Object.keys(changes).length === 0) return (callback || function() { })(null, this); - - this.__state.model.onSaving(this, changes, (function(err) { - if(err) return onError(err); - this.emit('saving', this, changes); - - this.__state.model.toSource(conditions); - _.merge(conditions, this.__state.model.uniqueConditions(this.__state.modified)); + var insert = Q.nbind(this.context.__state.model.collection.insert, this.context.__state.model.collection); + var update = Q.nbind(this.context.__state.model.collection.update, this.context.__state.model.collection); + var findOne = Q.nbind(this.context.__state.model.collection.findOne, this.context.__state.model.collection); + + if(this.context.__state.isNew) { + var toCreate = _.cloneDeep(this.context.__state.modified); + + promisePipe(this.context.__state.model.onCreating(toCreate).then((function(toCreate) { + this.context.emit('creating', toCreate); + + this.context.__state.model.toSource(toCreate); + return insert(toCreate, { w: 1 }); + }).bind(this)).then((function(created) { + this.context.__state.isNew = false; + this.context.__state.isPartial = false; + + return this.context.__state.model.onRetrieved(this.conditions, created[0], (function(value){ + this.__state.model.fromSource(value); + this.__state.original = _.cloneDeep(value); + this.__state.modified = _.cloneDeep(value); + this.__extendSchema(); + this.emit('retrieved', this); + }).bind(this.context), { partial: this.context.__state.isPartial }); + }).bind(this)), this.deferred); + } + else { + promisePipe(this.context.__state.model.onSaving(this.context, this.changes).then((function() { + this.context.emit('saving', this.context, this.changes); - this.__state.model.collection.update(conditions, changes, { w: 1 }, (function (err, changed) { - if (err) return onError(err); - if (!changed) return (callback || function () { })(null, this); + return update(this.conditions, this.changes, { w: 1 }); + }).bind(this)).then((function(changed) { + if(!changed) return Q(this.context); - var conditions = this.__state.model.uniqueConditions(this.__state.modified); - this.__state.model.collection.findOne(conditions, (function (err, latest) { - if (err) return onError(err); + var conditions = this.context.__state.model.uniqueConditions(this.context.__state.modified); + return findOne(conditions).then((function(latest) { if(!latest) { - this.__state.isNew = true; - return (callback || function () { })(null, this); + this.context.__state.isNew = true; + return Q(this.context); } - this.__state.model.onRetrieved(conditions, latest, callback || function () { }, (function (value) { + return this.context.__state.model.onRetrieved(conditions, latest, (function (value) { this.__state.model.fromSource(value); this.__state.isPartial = false; this.__state.original = _.cloneDeep(value); @@ -177,11 +153,16 @@ Instance.prototype.save = function(conditions, changes, callback) { this.__extendSchema(); this.emit('retrieved', this); return this; - }).bind(this)); + }).bind(this.context)); }).bind(this)); - }).bind(this)); - }).bind(this)); -}; + }).bind(this)).fail((function(err) { + this.emit('error', err); + return Q.reject(err); + }).bind(this.context)), this.deferred); + } + + return this.deferred.promise; +}).compile(); Instance.prototype.refresh = Instance.prototype.update = function(callback) { /// @@ -192,23 +173,20 @@ Instance.prototype.refresh = Instance.prototype.update = function(callback) { /// A function to be called once the update is complete /// - var onError = (function (err) { - this.emit('error', err); - if(callback) return callback(err); - else throw err; - }).bind(this); + var deferred = Q.defer(); + if(callback) promiseCallback(deferred.promise, callback); var conditions = this.__state.model.uniqueConditions(this.__state.original); - this.__state.model.collection.findOne(conditions, (function(err, latest) { - if(err) return onError(err); + var findOne = Q.nbind(this.__state.model.collection.findOne, this.__state.model.collection); + + promisePipe(findOne(conditions).then((function(latest) { if(!latest) { this.__state.isPartial = false; this.__state.isNew = true; this.__state.original = _.cloneDeep(this.__state.modified); - return this; + return Q(this); } - - this.__state.model.onRetrieved(conditions, latest, callback || function() { }, (function(value) { + return this.__state.model.onRetrieved(conditions, latest, (function(value) { this.__state.model.fromSource(value); this.__state.isNew = false; this.__state.isPartial = false; @@ -218,7 +196,12 @@ Instance.prototype.refresh = Instance.prototype.update = function(callback) { this.emit('retrieved', this); return this; }).bind(this)); - }).bind(this)); + }).bind(this)).fail((function(err) { + this.emit('error', err); + return Q.reject(err); + }).bind(this)), deferred); + + return deferred.promise; }; Instance.prototype.remove = Instance.prototype.delete = function(callback) { @@ -230,18 +213,30 @@ Instance.prototype.remove = Instance.prototype.delete = function(callback) { /// A function to be called when the object has been removed /// - if(this.__state.isNew) return (callback || function() { })(null, 0); + var deferred = Q.defer(); + if(callback) promiseCallback(deferred.promise, callback); + + if(this.__state.isNew) { + deferred.resolve(0); + return deferred.promise; + } var conditions = this.__state.model.uniqueConditions(this.__state.modified); this.__state.model.cache.drop(conditions, (function() { this.emit('removing', this); - this.__state.model.collection.remove(conditions, { w: callback ? 1 : 0 }, (function(err, removed) { - if(err) this.emit('error', err); - else this.emit('removed', this); + this.__state.model.collection.remove(conditions, { w: 1 }, (function(err, removed) { this.__state.isNew = true; - return callback(err, removed); + if(err) { + this.emit('error', err); + return deferred.reject(err); + } + + this.emit('removed', this); + return deferred.resolve(this); }).bind(this)); }).bind(this)); + + return deferred.promise; }; Instance.prototype.select = function(collection, filter) { @@ -378,3 +373,22 @@ Instance.forModel = function(model) { }; Instance.diff = diff; + + +/** + * PROMISES STUFF + */ + +function promisePipe(promise, deferred) { + promise.then(function(result) { + deferred.resolve(result); + }, function(err) { + deferred.reject(err); + }, function(progress) { + deferred.notify(progress); + }); +} + +function promiseCallback(promise, callback) { + promise.then(function(result) { callback(null, result); }, function(err) { callback(err); }); +} \ No newline at end of file