Skip to content

Commit

Permalink
Improved Instance base methods
Browse files Browse the repository at this point in the history
Uses prototype for reduced initialization overhead and lower memory consumption (since the code is no longer duplicated for each instance)
  • Loading branch information
notheotherben committed Dec 28, 2013
1 parent 2e20fef commit 1a51ddc
Showing 1 changed file with 172 additions and 183 deletions.
355 changes: 172 additions & 183 deletions lib/Instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,219 +25,208 @@ function Instance(model, doc, isNew) {
"use strict";

if(!(this instanceof Instance)) return new Instance(model, doc, isNew);

var $ = this;

if(!isNew) model.fromSource(doc);

var oldDoc = _.cloneDeep(doc);
var newDoc = _.cloneDeep(doc);

var schema = {};

function addSchemaProperties() {

for(var property in newDoc)
schema[property] = false;

for(var property in model.schema)
schema[property] = model.schema[property];

for(var targetProperty in schema) {
if(!$.hasOwnProperty(targetProperty))
(function(targetProperty) {
Object.defineProperty($, targetProperty, {
get: function() {
/// <value type="Object">Get the most recent value for this field</value>
return newDoc[targetProperty];
},
set: function(value) {
/// <value type="Object">Set the value of this field. Changes may be committed by calling save() on this instance.</value>
var validation = validate(schema[targetProperty], value, targetProperty, model.extraValidators);
if (!validation.passed) throw validation.toError();

newDoc[targetProperty] = value;
},
enumerable: true
});
})(targetProperty);
}
this.__state = {
model: model,
isNew: isNew,
original: _.cloneDeep(doc),
modified: _.cloneDeep(doc)
}

addSchemaProperties();
this.__extendSchema();

for (var methodName in model.options.methods) {
(function (methodName) {
Object.defineProperty($, methodName, {
value: function () {
return model.options.methods[methodName].apply($, arguments);
}
Object.defineProperty(this, methodName, {
value: (function () {
return model.options.methods[methodName].apply(this, arguments);
}).bind(this)
});
})(methodName);
}).bind(this)(methodName);
}

for (var propertyName in model.options.virtuals) {
(function (propertyName) {
if(typeof model.options.virtuals[propertyName] == 'function')
Object.defineProperty($, propertyName, {
get: function () {
return model.options.virtuals[propertyName].call($);
},
Object.defineProperty(this, propertyName, {
get: (function () {
return model.options.virtuals[propertyName].call(this);
}).bind(this),
enumerable: true
});
else
Object.defineProperty($, propertyName, {
get: function () {
return model.options.virtuals[propertyName].get.call($);
},
set: function(value) {
return model.options.virtuals[propertyName].set.call($, value);
},
Object.defineProperty(this, propertyName, {
get: (function () {
return model.options.virtuals[propertyName].get.call(this);
}).bind(this),
set: (function(value) {
return model.options.virtuals[propertyName].set.call(this, value);
}).bind(this),
enumerable: true
});
})(propertyName);
}).bind(this)(propertyName);
}

Object.defineProperty($, 'save', {
value: function (changes, cb) {
/// <signature>
/// <summary>Saves changes made to the current instance to the database without waiting for a response</summary>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database without waiting for a response</summary>
/// <param name="changes" type="Object">MongoDB changes query to be used instead of differential patching</param>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database</summary>
/// <param name="cb" type="Function">A function which is called when the save has been completed</param>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database</summary>
/// <param name="changes" type="Object">MongoDB changes query to be used instead of differential patching</param>
/// <param name="cb" type="Function">A function which is called when the save has been completed</param>
/// </signature>

if (!cb && typeof changes === 'function') {
cb = changes;
changes = null;
}
for(var i = 0; i < model.database.plugins.length; i++)
if(model.database.plugins[i].newInstance)
model.database.plugins[i].newInstance.call(this, model, doc, isNew);
}

if (!cb) cb = function () { };

var createObject = function (callback) {
var createDoc = _.cloneDeep(newDoc);

model.onCreating(createDoc, function(err) {
if (err) return callback(err);

model.toSource(createDoc);

model.collection.insert(createDoc, { w: 1 }, function (err, inserted) {
if (err) return callback(err);

isNew = false;
return model.onRetrieved(inserted[0], callback, function(value) {
model.fromSource(value);
oldDoc = _.cloneDeep(value);
newDoc = _.cloneDeep(value);
addSchemaProperties();
return $;
});
});
});
};
Instance.prototype.save = function(changes, callback) {
/// <signature>
/// <summary>Saves changes made to the current instance to the database without waiting for a response</summary>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database without waiting for a response</summary>
/// <param name="changes" type="Object">MongoDB changes query to be used instead of differential patching</param>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database</summary>
/// <param name="callback" type="Function">A function which is called when the save has been completed</param>
/// </signature>
/// <signature>
/// <summary>Saves changes made to the current instance to the database</summary>
/// <param name="changes" type="Object">MongoDB changes query to be used instead of differential patching</param>
/// <param name="callback" type="Function">A function which is called when the save has been completed</param>

if (!changes) {
var transformedOld = _.cloneDeep(oldDoc);
var transformedNew = _.cloneDeep(newDoc);
if(!callback && typeof changes == 'function') {
callback = changes;
changes = null;
}

model.toSource(transformedOld);
model.toSource(transformedNew);
function onError(err) {
if(callback) return callback(err);
else throw err;
}

changes = diffPatch(transformedOld, transformedNew);
}
if(this.__state.isNew) {
var toCreate = _.cloneDeep(this.__state.modified);

this.__state.model.onCreating(toCreate, (function(err) {
if(err) return onError(err);

this.__state.model.toSource(toCreate);
this.__state.model.collection.insert(toCreate, { w: 1 }, (function(err, created) {
if(err) return onError(err);

this.__state.isNew = false;
this.__state.model.onRetrieved(created[0], callback || function() { }, (function(value) {
this.__state.model.fromSource(value);
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
return this;
}).bind(this));
}).bind(this));
}).bind(this));
}

if (!!isNew || Object.keys(changes).length > 0) {
var runOptions = function () {

var conditions = model.uniqueConditions(newDoc);

model.onSaving($, changes, function(err) {
if(err) return cb(err);

model.collection.update(conditions, changes, { w: 1 }, function (err, updateCount) {
if (err) return cb(err);

if (updateCount > 0) {
model.collection.findOne(conditions, function (err, updated) {
if (err) return cb(err);

return model.onRetrieved(updated, cb, function(value) {
model.fromSource(value);
oldDoc = _.cloneDeep(value);
newDoc = _.cloneDeep(value);
addSchemaProperties();
return $;
});
});
} else return cb(null, $);
});
});
};

if (!isNew) return runOptions();
else createObject(cb);
}
else return cb(null, $);
},
enumerable: false
});

Object.defineProperty($, 'update', {
value: function (callback) {
/// <signature>
/// <summary>Updates this object from the database, bringing it up to date</summary>
/// </signature>
/// <signature>
/// <summary>Updates this object from the database, bringing it up to date</summary>
/// <param name="callback" type="Function">A function to be called once the update is complete</param>
/// </signature>

var conditions = model.uniqueConditions(oldDoc);

model.collection.findOne(conditions, function (err, updated) {
if (err) return cb(err);
return model.onRetrieved(updated,callback, function(value) {
model.fromSource(value);
oldDoc = _.cloneDeep(value);
newDoc = _.cloneDeep(value);
addSchemaProperties();
return $;
});
});
}
});
if(!changes) {
var original = _.cloneDeep(this.__state.original);
var modified = _.cloneDeep(this.__state.modified);

Object.defineProperty($, 'remove', {
value: function (callback) {
/// <summary>Removes this object from the database collection</summary>
/// <param name="callback" type="Function">A function to be called when the object has been removed</param>
this.__state.model.toSource(original);
this.__state.model.toSource(modified);

if (!isNew) {
var conditions = model.uniqueConditions(newDoc);
changes = Instance.diff(original, modified);
}

model.collection.remove(conditions, callback);
} else {
callback(null, 0); // No objects removed from the database
}
},
enumerable: false
});
if(Object.keys(changes).length === 0) return (callback || function() { })(null, this);

for(var i = 0; i < model.database.plugins.length; i++)
if(model.database.plugins[i].newInstance)
model.database.plugins[i].newInstance.call(this, model, doc, isNew);
}
this.__state.model.onSaving(this, changes, (function(err) {
if(err) return onError(err);

var conditions = this.__state.model.uniqueConditions(this.__state.modified);

this.__state.model.collection.update(conditions, changes, { w : 1 }, (function(err, changed) {
if(err) return onError(err);
if(!changed) return (callback || function() { })(null, this);

this.__state.model.collection.findOne(conditions, (function(err, latest) {
if(err) return onError(err);

this.__state.model.onRetrieved(latest, callback || function() { }, (function(value) {
this.__state.model.fromSource(value);
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
return this;
}).bind(this));
}).bind(this));
}).bind(this))
}).bind(this));
};

Instance.prototype.refresh = Instance.prototype.update = function(callback) {
/// <signature>
/// <summary>Updates this object from the database, bringing it up to date</summary>
/// </signature>
/// <signature>
/// <summary>Updates this object from the database, bringing it up to date</summary>
/// <param name="callback" type="Function">A function to be called once the update is complete</param>
/// </signature>

function onError(err) {
if(callback) return callback(err);
else throw err;
}

var conditions = this.__state.model.uniqueConditions(this.__state.original);
this.__state.model.collection.findOne(conditions, (function(err, latest) {
if(err) return onError(err);

this.__state.model.onRetrieved(latest, callback || function() { }, (function(value) {
this.__state.model.fromSource(value);
this.__state.original = _.cloneDeep(value);
this.__state.modified = _.cloneDeep(value);
this.__extendSchema();
return this;
}).bind(this));
}).bind(this));
};

Instance.prototype.remove = Instance.prototype.delete = function(callback) {
/// <summary>Removes this object from the database collection</summary>
/// <param name="callback" type="Function">A function to be called when the object has been removed</param>

if(this.__state.isNew) return (callback || function() { })(null, 0);

var conditions = this.__state.model.uniqueConditions(this.__state.modified);
this.__state.model.collection.remove(conditions, { w: callback ? 1 : 0 }, callback);
};

Instance.prototype.__extendSchema = function() {
var $ = this;

var schema = {};

for(var property in this.__state.modified)
schema[property] = false;

for(var property in this.__state.model.schema)
schema[property] = this.__state.model.schema[property];

for(var targetProperty in schema) {
if(!$.hasOwnProperty(targetProperty))
(function(targetProperty) {
Object.defineProperty($, targetProperty, {
get: function() {
/// <value type="Object">Get the most recent value for this field</value>
return $.__state.modified[targetProperty];
},
set: function(value) {
/// <value type="Object">Set the value of this field. Changes may be committed by calling save() on this instance.</value>
var validation = validate(schema[targetProperty], value, targetProperty, this.__state.model.extraValidators);
if (!validation.passed) throw validation.toError();
$.__state.modified[targetProperty] = value;
},
enumerable: true
});
})(targetProperty);
}
};

var diffPatch = Instance.diff = function (oldDoc, newDoc, path) {
/// <signature>
Expand Down

0 comments on commit 1a51ddc

Please sign in to comment.