diff --git a/src/prop_tests/dotNotation_test.js b/src/prop_tests/dotNotation_test.js new file mode 100644 index 0000000..126608a --- /dev/null +++ b/src/prop_tests/dotNotation_test.js @@ -0,0 +1,94 @@ +var QUnit = require("steal-qunit"); + +var set = require('../set-core'), + props = require("../props"); + +QUnit.module("can-set props.dotNotation"); + +/* + * For the dotNotation prop, we define sets like so: + * + * For a property 'n.p', with value 'IL' + * x ∈ X | x.n.p = 'IL' + * + */ +test('dotNotation set membership', function() { + /* + * For a property 'n.p', with value 'IL' + * x ∈ X | x.n.p == 'IL' + */ + var prop = props.dotNotation('n.p'), + alg = new set.Algebra(prop), + res = alg.has({'n.p': 'IL'}, {n:{p:'IL'}}); + ok(res, "object with nested property is member of set using dotNotation"); + + /* + * For a property 'n.p', with value 'IL' + * x ∉ X | x.n.p != 'IL' + */ + res = alg.has({'n.p': 'IL'}, {n:{p:'MI'}}); + ok(res === false, "object with nested property not a member of set using dotNotation"); + + /* + * For a property 'n.p.s', with value 'IL' + * x ∈ X | x.n.p.s == 'IL' + */ + prop = props.dotNotation('n.p.s'); + alg = new set.Algebra(prop); + res = alg.has({'n.p.s': 'IL'}, {n:{p:{s:'IL'}}}); + ok(res, "object with deep nested property is member of set using dotNotation"); +}); + +test('dotNotation set equality', function() { + var prop = props.dotNotation('n.p'), + alg = new set.Algebra(prop), + set1 = {'n.p': 'IL'}, + set2 = {'n.p': 'IL'}, + set3 = {'n.p': 'MI'}, + set4 = {n:{p:'MI'}}; + + /* + * {x | x ∈ X, x.n.p == 'IL'} = {y | y ∈ Y, y.n.p == 'IL'} + */ + ok(alg.equal(set1, set2) && alg.equal(set2, set1), "sets with dotNotation properties are equivalent"); + + /* + * {x | x ∈ X, x.n.p == 'IL'} != {y | y ∈ Y, y.n.p == 'MI'} + */ + ok(alg.equal(set1, set3) === false, "sets with dotNotation properties are not equivalent"); + + /* + * {x | x ∈ X, x.n.p == 'MI'} = {y | y ∈ Y, y.n.p == 'MI'} + */ + ok(alg.equal(set4, set3) === false, "sets with dotNotation properties are equivalent to sets with nested properties"); +}); + +test('dotNotation set subset', function() { + var alg = new set.Algebra( + props.dotNotation('address.state'), + props.dotNotation('address.city') + ), + set1 = {'address.state': 'IL'}, + set2 = {'address.state': 'IL', 'address.city': 'Chicago'}, + set3 = {address: {state: 'IL', city: 'Chicago'}}; + + /* + * {x | x ∈ X, x.address.state = 'IL', x.address.city = 'Chicago'} ⊆ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset(set2, set1), "sets with dotNotation property is a subset of another dotNotation set"); + + /* + * {x | x ∈ X, x.address.state = 'IL', x.address.city = 'Chicago'} ⊆ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset(set3, set1), "sets with nested property notation is a subset of a dotNotation set"); + + /* + * {y | y ∈ Y, y.address.state == 'IL'} ⊆ ξ + */ + ok(alg.subset(set1, {}), "sets with dotNotation properties are subsets of the universal set"); + + /* + * ξ ⊄ {y | y ∈ Y, y.address.state == 'IL'} + */ + ok(alg.subset({}, set1) === false, "the universal set is not a subset of a set with dotNotation"); +}); \ No newline at end of file diff --git a/src/props.js b/src/props.js index 8c99026..380f026 100644 --- a/src/props.js +++ b/src/props.js @@ -483,4 +483,51 @@ props.rangeInclusive = function(startIndexProperty, endIndexProperty){ }); }; +var nestedLookup = function(obj, propNameArray) { + if (obj === undefined) { + return undefined; + } + + if (propNameArray.length === 1) { + return obj[propNameArray[0]]; + } else { + return nestedLookup(obj[propNameArray[0]], propNameArray.slice(1)); + } +}; +/** + * @function can-set.props.dotNotation dotNotation + * @parent can-set.props + * + * @description Supports MongoDB-style 'dot notation' properties. + * + * @signature `set.props.dotNotation(dotProperty)` + * + * Defines a property that specifies a MongoDB-style nested property match. + * For example, a set property of "address.city" matches against the value of the nested `{address: {city}}` value. + * + * ``` + * set.props.dotNotation("address.city") + * ``` + * + * @param {String} dotProperty The MongoDB-style nested property name + * @return {can-set.compares} Returns a comparator used to build a set algebra + */ +props.dotNotation = function(dotProperty){ + var compares = new clause.Where({}); + + compares[dotProperty] = function(aVal, bVal, a, b, propertyName) { + // if the value wasn't picked out from the parent try a dotNotation lookup + if (aVal === undefined) { + aVal = nestedLookup(a, propertyName.split('.')); + } + if (bVal === undefined) { + bVal = nestedLookup(b, propertyName.split('.')); + } + + return aVal === bVal; + }; + + return compares; +}; + module.exports = props; diff --git a/src/props_test.js b/src/props_test.js index a29751e..39d200b 100644 --- a/src/props_test.js +++ b/src/props_test.js @@ -7,3 +7,4 @@ require('./prop_tests/rangeInclusive_test'); require('./prop_tests/offsetLimit_test'); require('./prop_tests/boolean_test'); require('./prop_tests/enum_test'); +require('./prop_tests/dotNotation_test');