From b796f5b0aaa76008cf81e53f8fc5ba830874714f Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 23 Jun 2014 18:20:18 -0700 Subject: [PATCH] model.save only updates changed data added deep-diff dependency --- lib/model.js | 23 +++++++++++++++++++++-- lib/mongo-diff.js | 39 +++++++++++++++++++++++++++++++++++++++ package.js | 6 +++--- smart.json | 4 +++- test/mongo-diff-test.js | 38 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 lib/mongo-diff.js create mode 100644 test/mongo-diff-test.js diff --git a/lib/model.js b/lib/model.js index 3addb98..0e9debb 100644 --- a/lib/model.js +++ b/lib/model.js @@ -2,6 +2,7 @@ Model = function(collectionName, obj, options) { options = options || {}; this.attributes = obj; + this.syncSavedState(); this._collection = Graviton._collections[collectionName]; @@ -80,11 +81,29 @@ Model.prototype.persist = function() { return false; }; +Model.prototype.syncSavedState = function() { + // create a deep clone. is there a better way to do this? + this._savedState = JSON.stringify(this.attributes); +}; + +Model.prototype.savedState = function() { + // only parse the saved state when needed for efficiency + if (_.isString(this._savedState)) this._savedState = JSON.parse(this._savedState); + return this._savedState; +}; + // insert or update // TODO: track the attributes that have changed and only update those Model.prototype.save = function() { if (!this.persist()) { - this._collection.update(this._id, {$set: _.omit(this.attributes, '_id')}); + var diff = Graviton.mongoDiff(this.savedState(), this.attributes); + if (!_.isEmpty(diff.operators)) { + this._collection.update(this._id, diff.operators); + this.syncSavedState(); + return {updated: true, warnings: diff.warnings}; + } + return false; } - return this; + this.syncSavedState(); + return {inserted: true}; }; diff --git a/lib/mongo-diff.js b/lib/mongo-diff.js new file mode 100644 index 0000000..afb0157 --- /dev/null +++ b/lib/mongo-diff.js @@ -0,0 +1,39 @@ +var MongoDiff = function(lhs, rhs, prefilter) { + this._lhs = lhs; + this._rhs = rhs; + this._deepDiffs = DeepDiff.diff(lhs, rhs, prefilter); + this.operators = {}; + this.warnings = []; + for (var i in this._deepDiffs) { + this.addDeepDiff(this._deepDiffs[i]); + } +}; + +MongoDiff.prototype.addOperator = function(operator, path, val) { + var obj = this.operators[operator] = this.operators[operator] || {}; + obj[path] = val; +}; + +MongoDiff.prototype.addDeepDiff = function(diff) { + var path = diff.path.join('.'); + switch (diff.kind) { + case 'A': + var set = this.operators.$set; + if (!(set && set[path])) { + this.addOperator("$set", path, Graviton.getProperty(this._rhs, path)); + this.warnings.push("Will replace entire array at: '"+path+"'. Individual updates should probably be done manually instead."); + } + break; + case 'E': + case 'N': + this.addOperator("$set", path, diff.rhs); + break; + case 'D': + this.addOperator("$unset", path, ""); + break; + } +}; + +Graviton.mongoDiff = function(lhs, rhs, prefilter) { + return new MongoDiff(lhs, rhs, prefilter); +}; diff --git a/package.js b/package.js index a19112c..b2a3604 100644 --- a/package.js +++ b/package.js @@ -4,10 +4,10 @@ Package.describe({ Package.on_use(function (api, where) { api.use(["underscore"], ["client", "server"]); - api.add_files(['lib/model.js', 'lib/relations.js', 'graviton.js'], ['client', 'server']); + api.use(['deep-diff'], ['client', 'server']); + api.add_files(['lib/model.js', 'lib/relations.js', 'graviton.js', 'lib/mongo-diff.js'], ['client', 'server']); if (typeof api.export !== 'undefined') { - // api.export("Model", ["client", "server"]); api.export("Graviton", ["client", "server"]); } }); @@ -15,7 +15,7 @@ Package.on_use(function (api, where) { Package.on_test(function (api) { api.use(['graviton', 'tinytest', 'test-helpers']); - api.add_files('test/graviton-test.js', ['client', 'server']); + api.add_files(['test/graviton-test.js', 'test/mongo-diff-test.js'], ['client', 'server']); }); diff --git a/smart.json b/smart.json index 2ae7ae3..6839a2b 100644 --- a/smart.json +++ b/smart.json @@ -5,5 +5,7 @@ "author": "Jeremy Dunn", "version": "0.0.2", "git": "https://github.com/fractallian/graviton.git", - "packages": {} + "packages": { + "deep-diff": "0.0.1" + } } \ No newline at end of file diff --git a/test/mongo-diff-test.js b/test/mongo-diff-test.js new file mode 100644 index 0000000..f2ff6da --- /dev/null +++ b/test/mongo-diff-test.js @@ -0,0 +1,38 @@ +var lhs = { + name: 'my object', + description: 'it\'s an object!', + details: { + it: 'has', + an: 'array', + with: ['a', 'few', 'elements'], + array: ['inside', {another: ['array']}] + } +}; + +var rhs = { + name: 'updated object', + description: 'it\'s an object!', + details: { + it: 'has', + an: 'array', + with: ['a', 'few', 'more', 'elements', { than: 'before' }], + array: ['inside', {another: ['array', 'should not be tested']}] + } +}; + + +var expected = { + $set: { + name: 'updated object', + 'details.with': ['a', 'few', 'more', 'elements', { than: 'before' }], + 'details.array': ['inside', {another: ['array', 'should not be tested']}] + } +}; + +var mongoDiff = Graviton.mongoDiff(lhs, rhs); + + +Tinytest.add('mongo diff', function(test) { + test.isTrue(_.isEqual(mongoDiff.operators, expected)); + test.equal(mongoDiff.warnings.length, 2); +}); \ No newline at end of file