Skip to content

Commit

Permalink
✨ Lazy get (#266)
Browse files Browse the repository at this point in the history
* ✨ Support first arg currying in get

* ✨ Apply: support function args

* 🐛 fix broken update...
  • Loading branch information
nlepage committed Mar 8, 2018
1 parent a7616df commit a2a34aa
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 14 deletions.
41 changes: 28 additions & 13 deletions packages/immutadot/src/core/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,46 @@ import {
index,
prop,
} from 'path/consts'
import { isNil } from 'util/lang'
import {
isNil,
isString,
} from 'util/lang'
import { toPath } from 'path/toPath'

const getter = Symbol('getter')

/**
* Gets the value at <code>path</code> of <code>obj</code>.
* @memberof core
* @param {*} obj The object.
* @param {*} [obj] The object.
* @param {string|Array} path The path of the property to get.
* @param {*} defaultValue The default value.
* @return {*} Returns the value or <code>defaultValue</code>.
* @example get({ nested: { prop: 'val' } }, 'nested.prop') // => 'val'
* @example get({ nested: { prop: 'val' } }, 'nested.unknown', 'default') // => 'default'
* @since 1.0.0
*/
function get(obj, path, defaultValue) {
function walkPath(curObj, remPath) {
if (remPath.length === 0) return curObj === undefined ? defaultValue : curObj
if (isNil(curObj)) return defaultValue
const [[, prop], ...pathRest] = remPath
return walkPath(curObj[prop], pathRest)
function get(...args) {
const [firstArg, ...argsRest] = args
if (isString(firstArg)) return makeGetter(...args)
return makeGetter(...argsRest)(firstArg)
}

function makeGetter(path, defaultValue) {
const getterFn = function(obj) {
function walkPath(curObj, remPath) {
if (remPath.length === 0) return curObj === undefined ? defaultValue : curObj
if (isNil(curObj)) return defaultValue
const [[, prop], ...pathRest] = remPath
return walkPath(curObj[prop], pathRest)
}
const parsedPath = toPath(path)
if (parsedPath.some(([propType]) => propType !== prop && propType !== index))
throw TypeError('get supports only properties and array indexes in path')
return walkPath(obj, parsedPath)
}
const parsedPath = toPath(path)
if (parsedPath.some(([propType]) => propType !== prop && propType !== index))
throw TypeError('get supports only properties and array indexes in path')
return walkPath(obj, parsedPath)
getterFn[getter] = true
return getterFn
}

export { get }
export { get, getter }
5 changes: 5 additions & 0 deletions packages/immutadot/src/core/get.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ describe('core.get', () => {
it('should throw an error if path includes something else than props and indexes', () => {
expect(() => get({}, 'foo[1:2]')).toThrowError('get supports only properties and array indexes in path')
})

it('should support currying the object', () => {
expect(get('nested2.arr[0].val')(obj)).toBe('arrVal')
expect(get('nested2.arr[1].val', 'defaultValue')(obj)).toBe('defaultValue')
})
})
5 changes: 4 additions & 1 deletion packages/immutadot/src/path/apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
length,
} from 'util/lang'

import { getter } from 'core/get'

import { toPath } from './toPath'

/**
Expand Down Expand Up @@ -132,7 +134,8 @@ const apply = operation => {

if (remPath.length === 1) {
const newObj = copyIfNecessary(curObj, propType, doCopy)
operation(newObj, propValue, value, ...args)
const resolvedArgs = args.map(arg => arg[getter] ? arg(obj) : arg)
operation(newObj, propValue, value, ...resolvedArgs)
return [false, newObj]
}

Expand Down
25 changes: 25 additions & 0 deletions packages/immutadot/src/path/apply.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-env jest */
import { apply } from './apply'
import { get } from 'core/get'
import { immutaTest } from 'test.utils'

describe('path.apply', () => {
Expand Down Expand Up @@ -266,4 +267,28 @@ describe('path.apply', () => {
'nested.prop2.val',
)
})

it('should support lazy function args', () => {
immutaTest(
input => {
const output = inc(input, 'nested.prop1.val', get('nested.prop2.val'))
expect(output).toEqual({
nested: {
prop1: { val: 7 },
prop2: { val: 4 },
},
other: {},
})
return output
},
{
nested: {
prop1: { val: 3 },
prop2: { val: 4 },
},
other: {},
},
'nested.prop1.val',
)
})
})

0 comments on commit a2a34aa

Please sign in to comment.