Repository: goldibex/targaryen
Pull request #104 (2016-12-15 17:10:53)

Add `DataNode.$merge` (`Database.root.$merge`)
    DataNode.$merge(path: string, patch: object, now: undefined|number): DataNode

Low level merge method used by `Database.update(path, patch, now)`. Support multi-location merge.


Patch:
diff --git a/lib/database/index.js b/lib/database/index.js
index 0a58696..4b4f4db 100644
--- a/lib/database/index.js
+++ b/lib/database/index.js
@@ -143,19 +143,11 @@ class Database {
 
     now = now || Date.now();
 
-    let newRoot = this.root;
-
-    const pathsToTest = Object.keys(patch).map(endPath => {
-      const pathToNode = paths.join(path, endPath);
-
-      newRoot = newRoot.$set(pathToNode, patch[endPath], undefined, now);
-
-      return pathToNode;
-    });
-
-    const newData = this.with({data: newRoot, now});
+    const data = this.root.$merge(path, patch, now);
+    const newDatabase = this.with({data, now});
+    const pathsToTest = Object.keys(patch).map(endPath => paths.join(path, endPath));
     const writeResults = pathsToTest.map(
-      p => this.rules.tryWrite(p, this, newData, patch, now)
+      p => this.rules.tryWrite(p, this, newDatabase, patch, now)
     );
 
     return results.update(path, this, patch, writeResults);
diff --git a/lib/database/store.js b/lib/database/store.js
index d705b77..c9249aa 100644
--- a/lib/database/store.js
+++ b/lib/database/store.js
@@ -313,6 +313,25 @@ class DataNode {
 
   }
 
+  /**
+   * Return a copy of this node with the data replaced at the path locations.
+   *
+   * @param  {string} path  A root location for all the node to update
+   * @param  {object} patch Map of path (relative to the root location above) to their new data
+   * @param  {number} [now] This update timestamp
+   * @return {DataNode}
+   */
+  $merge(path, patch, now) {
+    path = paths.trim(path);
+    now = now || Date.now();
+
+    return Object.keys(patch).reduce((node, endPath) => {
+      const pathToNode = paths.join(path, endPath);
+
+      return node.$set(pathToNode, patch[endPath], undefined, now);
+    }, this);
+  }
+
   /**
    * Return a copy of the node with node at the path location.
    *


Test patch:
diff --git a/test/spec/lib/database/store.js b/test/spec/lib/database/store.js
index 168fe6a..0aaa04f 100644
--- a/test/spec/lib/database/store.js
+++ b/test/spec/lib/database/store.js
@@ -224,6 +224,55 @@ describe('store', function() {
 
   });
 
+  describe('#$merge', function() {
+    let data;
+
+    beforeEach(function() {
+      data = store.create({
+        a: 1,
+        b: {
+          c: {
+            d: 2
+          }
+        }
+      });
+    });
+
+    it('should return a new tree with merged data', function() {
+      const original = data.$value();
+      const newRoot = data.$merge('/', {a: 3, 'b/c/e': 4});
+
+      expect(data.$value()).to.deep.equal(original);
+      expect(newRoot.$value()).to.deep.equal({
+        a: 3,
+        b: {
+          c: {
+            d: 2,
+            e: 4
+          }
+        }
+      });
+    });
+
+    it('should return a new tree with removed branches', function() {
+      const newRoot = data.$merge('/', {a: null});
+
+      expect(data.a.$value()).to.equal(1);
+      expect(newRoot).not.to.have.property('a');
+    });
+
+    it('should return a new tree without empty branches', function() {
+      const newRoot = data.$merge('b/c', {d: null, e: null, f: 3});
+
+      expect(newRoot.b.c.$value()).to.deep.equal({f: 3});
+    });
+
+    it('should handle empty patch', function() {
+      expect(data.$merge('b/c', {})).to.equal(data);
+    });
+
+  });
+
   describe('#$remove', function() {
     let data;