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