Repository: ViacomInc/data-point
Pull request #59 (2017-12-19 14:06:30)

Create path reducer functions
This moves the path reducer logic into the factory instead of the reducer.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/14727256/badge)](https://coveralls.io/builds/14727256)

Coverage remained the same at 100.0% when pulling **5684761001b86fb470603a9279c5c1ddca0be9a8 on raingerber:create-path-reducer-functions** into **f4989f763cf3aa5f50d45db0bef5eb969a8919ae on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/14746045/badge)](https://coveralls.io/builds/14746045)

Coverage remained the same at 100.0% when pulling **2e78d8880e56728f3d0bb118fbcbd4ec167c9c31 on raingerber:create-path-reducer-functions** into **f4989f763cf3aa5f50d45db0bef5eb969a8919ae on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/14779693/badge)](https://coveralls.io/builds/14779693)

Coverage remained the same at 100.0% when pulling **8e870451e6d20d754953bf8fe7fcb1b7d784038c on raingerber:create-path-reducer-functions** into **22c137e418abd6eef15237893251c7f930269919 on ViacomInc:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/14780743/badge)](https://coveralls.io/builds/14780743)

Coverage remained the same at 100.0% when pulling **465fd47e5cd7d5d5a35cb9c7a3537115f68896d9 on raingerber:create-path-reducer-functions** into **336b6ade7e85d7d4581c12a660493e9516409ee8 on ViacomInc:master**.

acatl: closes  #74
coveralls: 
[![Coverage Status](https://coveralls.io/builds/14781113/badge)](https://coveralls.io/builds/14781113)

Coverage remained the same at 100.0% when pulling **542fa1929e9f678c1d30a899ba51f1879d7456dc on raingerber:create-path-reducer-functions** into **336b6ade7e85d7d4581c12a660493e9516409ee8 on ViacomInc:master**.


Patch:
diff --git a/packages/data-point/lib/reducer-path/factory.js b/packages/data-point/lib/reducer-path/factory.js
index e13e765..685105f 100644
--- a/packages/data-point/lib/reducer-path/factory.js
+++ b/packages/data-point/lib/reducer-path/factory.js
@@ -13,20 +13,95 @@ const REDUCER_PATH = (module.exports.type = 'ReducerPath')
 function ReducerPath () {
   this.type = REDUCER_PATH
   this.name = ''
+  this.body = undefined
   this.asCollection = false
 }
 
 module.exports.ReducerPath = ReducerPath
 
+/**
+ * @param {*} source
+ * @returns {boolean}
+ */
 function isPath (source) {
   return _.isString(source) && source.charAt(0) === '$'
 }
 
 module.exports.isPath = isPath
 
+/**
+ * @param {Accumulator} acc
+ * @returns {*}
+ */
+function getAccumulatorValue (acc) {
+  return acc.value
+}
+
+module.exports.getAccumulatorValue = getAccumulatorValue
+
+/**
+ * @param {string} jsonPath
+ * @param {Accumulator} acc
+ * @returns {*}
+ */
+function getFromAccumulator (jsonPath, acc) {
+  return _.get(acc, jsonPath)
+}
+
+module.exports.getFromAccumulator = getFromAccumulator
+
+/**
+ * @param {string} jsonPath
+ * @param {Accumulator} acc
+ * @returns {*}
+ */
+function getFromAccumulatorValue (jsonPath, acc) {
+  return _.get(acc.value, jsonPath)
+}
+
+module.exports.getFromAccumulatorValue = getFromAccumulatorValue
+
+/**
+ * @param {string} jsonPath
+ * @param {Accumulator} acc
+ * @returns {*}
+ */
+function mapFromAccumulatorValue (jsonPath, acc) {
+  if (Array.isArray(acc.value)) {
+    return _.map(acc.value, jsonPath)
+  }
+
+  return null
+}
+
+module.exports.mapFromAccumulatorValue = mapFromAccumulatorValue
+
+/**
+ * @param {string} jsonPath
+ * @param {boolean} asCollection
+ * @returns {Function}
+ */
+function getPathReducerFunction (jsonPath, asCollection) {
+  if (jsonPath === '.' || _.isEmpty(jsonPath)) {
+    return getAccumulatorValue
+  }
+
+  if (jsonPath.startsWith('..')) {
+    return getFromAccumulator.bind(null, jsonPath.slice(2))
+  }
+
+  if (asCollection) {
+    return mapFromAccumulatorValue.bind(null, jsonPath)
+  }
+
+  return getFromAccumulatorValue.bind(null, jsonPath)
+}
+
+module.exports.getPathReducerFunction = getPathReducerFunction
+
 /**
  * parse reducer
- * @param  {string} reducerRaw raw reducer path
+ * @param {string} source - raw reducer path
  * @return {reducer}
  */
 function create (source) {
@@ -34,6 +109,7 @@ function create (source) {
 
   reducer.asCollection = source.slice(-2) === '[]'
   reducer.name = _.defaultTo(source.substr(1), '.').replace(/\[]$/, '')
+  reducer.body = getPathReducerFunction(reducer.name, reducer.asCollection)
 
   return Object.freeze(reducer)
 }
diff --git a/packages/data-point/lib/resolve/reducer-path.js b/packages/data-point/lib/resolve/reducer-path.js
index a6f204a..fc956f2 100644
--- a/packages/data-point/lib/resolve/reducer-path.js
+++ b/packages/data-point/lib/resolve/reducer-path.js
@@ -1,56 +1,16 @@
 'use strict'
 
-const _ = require('lodash')
 const utils = require('../utils')
 
 /**
- * resolve the JSON Object path from given value
- *
- * @param {Object} value
- * @param {string} jsonPath
- * @returns
- */
-function resolveObjectPath (acc, jsonPath, reducerPath) {
-  if (typeof reducerPath === 'undefined') {
-    reducerPath = {}
-  }
-
-  const type = reducerPath.type || ''
-  const asCollection = reducerPath.asCollection || false
-
-  if (jsonPath === '.' || _.isEmpty(jsonPath)) {
-    return acc.value
-  }
-
-  if (jsonPath.substring(0, 2) === '..') {
-    return _.get(acc, jsonPath.slice(2))
-  }
-
-  if (type === 'ReducerPath' && asCollection) {
-    if (!Array.isArray(acc.value)) {
-      return null
-    }
-
-    return _.map(acc.value, jsonPath)
-  }
-
-  return _.get(acc.value, jsonPath)
-}
-
-module.exports.resolveObjectPath = resolveObjectPath
-
-/**
- * resolve a reducer json path from a accumulator object
- *
- * @param {Object} store
- * @param {function} resolveReducer
- * @param {Accumulator} accumulator current accumulator
+ * @param {Accumulator} accumulator - current accumulator
  * @param {ReducerPath} reducerPath
  * @returns {Promise<Accumulator>}
  */
-function resolve (store, resolveReducer, accumulator, reducerPath) {
-  const value = resolveObjectPath(accumulator, reducerPath.name, reducerPath)
-  return Promise.resolve(utils.set(accumulator, 'value', value))
+function resolve (accumulator, reducerPath) {
+  const value = reducerPath.body(accumulator)
+  const acc = utils.set(accumulator, 'value', value)
+  return Promise.resolve(acc)
 }
 
 module.exports.resolve = resolve
diff --git a/packages/data-point/lib/resolve/reducer.js b/packages/data-point/lib/resolve/reducer.js
index f9c4a13..302c261 100644
--- a/packages/data-point/lib/resolve/reducer.js
+++ b/packages/data-point/lib/resolve/reducer.js
@@ -19,11 +19,7 @@ function getReducerFunction (store, reducerType) {
   /* eslint indent: ["error", 2, { "SwitchCase": 1 }] */
   switch (reducerType) {
     case 'ReducerPath':
-      reducerResolver = _.partial(
-        resolveReducerPath.resolve,
-        store.filters,
-        resolve
-      )
+      reducerResolver = resolveReducerPath.resolve
       break
     case 'ReducerFunction':
       reducerResolver = resolveReducerFunction.resolve


Test patch:
diff --git a/packages/data-point/lib/reducer-path/factory.test.js b/packages/data-point/lib/reducer-path/factory.test.js
index 6e2af07..14aef8b 100644
--- a/packages/data-point/lib/reducer-path/factory.test.js
+++ b/packages/data-point/lib/reducer-path/factory.test.js
@@ -8,6 +8,60 @@ it('reducer/reducer-path#isPath', () => {
   expect(factory.isPath('$.')).toBeTruthy()
 })
 
+describe('ReducerPath getters', () => {
+  let acc
+  beforeEach(() => {
+    acc = {
+      params: {
+        a: [2, 4]
+      },
+      value: [
+        {
+          a: 2
+        },
+        {
+          a: 4
+        }
+      ]
+    }
+  })
+
+  it('reducer/reducer-path#getAccumulatorValue', () => {
+    expect(factory.getAccumulatorValue({ value: undefined })).toBeUndefined()
+    expect(factory.getAccumulatorValue(acc)).toEqual(acc.value)
+  })
+
+  it('reducer/reducer-path#getFromAccumulator', () => {
+    expect(factory.getFromAccumulator('', acc)).toBeUndefined()
+    expect(factory.getFromAccumulator('value', acc)).toEqual(acc.value)
+    expect(factory.getFromAccumulator('params', acc)).toEqual(acc.params)
+    expect(factory.getFromAccumulator('params.a', acc)).toEqual([2, 4])
+    expect(factory.getFromAccumulator('params.a[1]', acc)).toBe(4)
+  })
+
+  it('reducer/reducer-path#getFromAccumulatorValue', () => {
+    expect(factory.getFromAccumulatorValue('', acc)).toBeUndefined()
+    expect(factory.getFromAccumulatorValue('value', acc)).toBeUndefined()
+    expect(factory.getFromAccumulatorValue('[0]', acc)).toEqual(acc.value[0])
+  })
+
+  it('reducer/reducer-path#mapFromAccumulatorValue', () => {
+    expect(factory.mapFromAccumulatorValue('a', { value: {} })).toBeNull()
+    expect(factory.mapFromAccumulatorValue('a', acc)).toEqual([2, 4])
+  })
+})
+
+describe('reducer/reducer-path#getPathReducerFunction', () => {
+  it('should always return a function', () => {
+    expect(factory.getPathReducerFunction()).toBeInstanceOf(Function)
+    expect(factory.getPathReducerFunction('')).toBeInstanceOf(Function)
+    expect(factory.getPathReducerFunction('.')).toBeInstanceOf(Function)
+    expect(factory.getPathReducerFunction('..')).toBeInstanceOf(Function)
+    expect(factory.getPathReducerFunction('..', true)).toBeInstanceOf(Function)
+    expect(factory.getPathReducerFunction('a.b.c')).toBeInstanceOf(Function)
+  })
+})
+
 describe('reducer/reducer-path#create', () => {
   it('basic path', () => {
     const reducer = factory.create('$a')
@@ -33,3 +87,116 @@ describe('reducer/reducer-path#create', () => {
     expect(reducer.asCollection).toBe(true)
   })
 })
+
+describe('ReducerPath#body', () => {
+  test('resolve "empty" jsonpath to entire value', () => {
+    const acc = {
+      value: 'test'
+    }
+    const result = factory.create('$').body(acc)
+    expect(result).toBe('test')
+  })
+
+  test('resolve "$." to entire value', () => {
+    const acc = {
+      value: 'test'
+    }
+    const result = factory.create('$.').body(acc)
+    expect(result).toBe('test')
+  })
+
+  test('resolve prefix ".." with valid jsonpath to resolved value', () => {
+    const acc = {
+      value: {
+        a: ['test']
+      },
+      locals: 'test2'
+    }
+    let result = factory.create('$..value.a[0]').body(acc)
+    expect(result).toBe('test')
+    result = factory.create('$..locals').body(acc)
+    expect(result).toBe('test2')
+  })
+
+  test('resolve valid jsonpath to resolved value', () => {
+    const acc = {
+      value: {
+        a: ['test']
+      }
+    }
+    const result = factory.create('$a[0]').body(acc)
+    expect(result).toBe('test')
+  })
+
+  test('resolve valid collection path to resolved value', () => {
+    const acc = {
+      value: [
+        {
+          a: {
+            b: {
+              c: 1
+            }
+          }
+        },
+        {
+          a: {
+            b: {
+              c: 2
+            }
+          }
+        },
+        {
+          a: {
+            b: {
+              c: 3
+            }
+          }
+        }
+      ]
+    }
+    const result = factory.create('$a.b.c[]').body(acc)
+    expect(result).toEqual([1, 2, 3])
+  })
+
+  test('resolve invalid collection path key to array of undefined', () => {
+    const acc = {
+      value: [
+        {
+          a: {
+            b: {
+              c: 1
+            }
+          }
+        },
+        {
+          a: {
+            b: {
+              c: 2
+            }
+          }
+        },
+        {
+          a: {
+            b: {
+              c: 3
+            }
+          }
+        }
+      ]
+    }
+    const result = factory.create('$a.b.d[]').body(acc)
+    expect(result).toEqual([undefined, undefined, undefined])
+  })
+
+  test('resolve invalid collection path to null', () => {
+    const acc = {
+      value: {
+        a: {
+          b: 'c'
+        }
+      }
+    }
+    const result = factory.create('$a.b.c[]').body(acc)
+    expect(result).toBe(null)
+  })
+})
diff --git a/packages/data-point/lib/resolve/reducer-path.test.js b/packages/data-point/lib/resolve/reducer-path.test.js
index 85b881b..9503532 100644
--- a/packages/data-point/lib/resolve/reducer-path.test.js
+++ b/packages/data-point/lib/resolve/reducer-path.test.js
@@ -3,145 +3,7 @@
 
 const AccumulatorFactory = require('../accumulator/factory')
 const reducerFactory = require('../reducer/factory')
-const reducerPathFactory = require('../reducer-path/factory')
 const reducerPath = require('./reducer-path')
-const resolveReducer = require('./reducer')
-
-const fixtureStore = require('../../test/utils/fixture-store')
-
-let store
-
-beforeAll(() => {
-  store = fixtureStore.create()
-})
-
-describe('resolve#reducer-path.resolveObjectPath', () => {
-  test('resolve $. to entire value', () => {
-    const acc = {
-      value: 'test'
-    }
-    const result = reducerPath.resolveObjectPath(acc, '.')
-    expect(result).toBe('test')
-  })
-
-  test('resolve "empty" jsonpath to entire value', () => {
-    const acc = {
-      value: 'test'
-    }
-    const result = reducerPath.resolveObjectPath(acc)
-    expect(result).toBe('test')
-  })
-
-  test('resolve valid jsonpath to resolved value', () => {
-    const acc = {
-      value: {
-        a: ['test']
-      }
-    }
-    const result = reducerPath.resolveObjectPath(acc, 'a[0]')
-    expect(result).toBe('test')
-  })
-
-  test('resolve valid collection path to resolved value', () => {
-    const acc = {
-      value: [
-        {
-          a: {
-            b: {
-              c: 1
-            }
-          }
-        },
-        {
-          a: {
-            b: {
-              c: 2
-            }
-          }
-        },
-        {
-          a: {
-            b: {
-              c: 3
-            }
-          }
-        }
-      ]
-    }
-    const reducerPathInstance = reducerPathFactory.create('$a.b.c[]')
-    const result = reducerPath.resolveObjectPath(
-      acc,
-      reducerPathInstance.name,
-      reducerPathInstance
-    )
-    expect(result).toEqual([1, 2, 3])
-  })
-
-  test('resolve invalid collection path key to array of undefined', () => {
-    const acc = {
-      value: [
-        {
-          a: {
-            b: {
-              c: 1
-            }
-          }
-        },
-        {
-          a: {
-            b: {
-              c: 2
-            }
-          }
-        },
-        {
-          a: {
-            b: {
-              c: 3
-            }
-          }
-        }
-      ]
-    }
-    const reducerPathInstance = reducerPathFactory.create('$a.b.d[]')
-    const result = reducerPath.resolveObjectPath(
-      acc,
-      reducerPathInstance.name,
-      reducerPathInstance
-    )
-    expect(result).toEqual([undefined, undefined, undefined])
-  })
-
-  test('resolve invalid collection path to null', () => {
-    const acc = {
-      value: {
-        a: {
-          b: 'c'
-        }
-      }
-    }
-    const reducerPathInstance = reducerPathFactory.create('$a.b.c[]')
-    const result = reducerPath.resolveObjectPath(
-      acc,
-      reducerPathInstance.name,
-      reducerPathInstance
-    )
-    expect(result).toBe(null)
-  })
-
-  test('resolve prefixe ".." with valid jsonpath to resolved value', () => {
-    const acc = {
-      value: {
-        a: ['test']
-      },
-      locals: 'test2'
-    }
-    let result = reducerPath.resolveObjectPath(acc, '..value.a[0]')
-    expect(result).toBe('test')
-    result = reducerPath.resolveObjectPath(acc, '..locals')
-    expect(result).toBe('test2')
-  })
-})
 
 describe('resolve#transform.resolve', () => {
   function resolve (value, rawReducer) {
@@ -153,12 +15,7 @@ describe('resolve#transform.resolve', () => {
       locals
     })
 
-    return reducerPath.resolve(
-      store,
-      resolveReducer.resolve,
-      filtercontext,
-      reducerFactory.create(rawReducer)
-    )
+    return reducerPath.resolve(filtercontext, reducerFactory.create(rawReducer))
   }
 
   test('resolve current value', () => {