defineComparison
QueryLogic.defineComparison(TypeA, TypeB, comparisons)
This registers comparison operators between the TypeA
and TypeB
types. For example, the following might define how to compare
the GreaterThan
type to the LessThan
type:
import {QueryLogic} from "can";
QueryLogic.defineComparison(GreaterThan, LessThan, {
union(greaterThan, lessThan){
if(greaterThan.value < lessThan.value) {
return QueryLogic.UNIVERSAL
} else {
return new QueryLogic.ValueOr([greaterThan, lessThan]);
}
},
intersection(greaterThan, lessThan){
if(greaterThan.value < lessThan.value) {
return new QueryLogic.ValueAnd([greaterThan, lessThan]);
} else {
return QueryLogic.EMPTY;
}
},
difference(greaterThan, lessThan){
if(greaterThan.value < lessThan.value) {
return new QueryLogic.GreaterThanEqual(lessThan.value)
} else {
return greaterThan;
}
}
});
Note, for comparisons of two different types, you will also want to
define the reverse difference
like:
import {QueryLogic} from "can";
QueryLogic.defineComparison(LessThan, GreaterThan, {
difference(lessThan, greaterThan) {
if(greaterThan.value < lessThan.value) {
return new QueryLogic.LessThanEqual(lessThan.value);
} else {
return lessThan;
}
}
});
union
and intersection
isn't necessary because they are symmetrical (Ex: a U b = b U a
).
Also, for many types you will want to define the difference between the UNIVERSAL set and the type:
import {QueryLogic} from "can";
QueryLogic.defineComparison(QueryLogic.UNIVERSAL, GreaterThan, {
difference(universe, greaterThan) {
return new QueryLogic.LessThanEqual(greaterThan.value);
}
})
Parameters
- TypeA
{function}
:A constructor function.
- TypeB
{function}
:A constructor function.
- comparisons
{Object}
:An object of one or more of the following comparison functions:
union(typeAInstance, typeBInstance)
- Returns a representation of the union of the two sets.intersection(typeAInstance, typeBInstance)
- Returns a representation of the intersection of the two sets.difference(typeAInstance, typeBInstance)
- Returns a representation of the difference of the two sets.
The special sets can be returned from these functions to indicate:
- EMPTY - The empty set.
- UNDEFINABLE - A representation of the result of the operation exists, but there is no way to express it.
- UNKNOWABLE - It is unknown if a representation of the operations result exists.
Use
If you want total control over filtering logic, you can create a SetType
that
provides the following:
- methods:
can.isMember
- A function that returns if an object belongs to the query.can.serialize
- A function that returns the serialized form of the type for the query.
- comparisons:
union
- The result of taking a union of twoSetType
s.intersection
- The result of taking an intersection of twoSetType
s.difference
- The result of taking a difference of twoSetType
s.
The following creates a SearchableStringSet
that is able to perform searches that match
the provided text like:
import {canReflect, QueryLogic} from "can";
// Takes the value of `name` (ex: `"chicken"`)
function SearchableStringSet(value) {
this.value = value;
}
canReflect.assignSymbols(SearchableStringSet.prototype,{
// Returns if the name on a todo is actually a member of the set.
"can.isMember": function(value){
return value.includes(this.value);
},
// Converts back to a value that can be in a query.
"can.serialize": function(){
return this.value;
}
});
// Specify how to do the fundamental set comparisons.
QueryLogic.defineComparison(SearchableStringSet,SearchableStringSet,{
// Return a set that would load all records in searchA and searchB.
union(searchA, searchB){
// If searchA's text contains searchB's text, then
// searchB will include searchA's results.
if(searchA.value.includes(searchB.value)) {
// A:`food` ∪ B:`foo` => `foo`
return searchB;
}
if(searchB.value.includes(searchA.value)) {
// A:`foo` ∪ B:`food` => `foo`
return searchA;
}
// A:`ice` ∪ B:`cream` => `ice` || `cream`
return new QueryLogic.ValueOr([searchA, searchB]);
},
// Return a set that would load records shared by searchA and searchB.
intersection(searchA, searchB){
// If searchA's text contains searchB's text, then
// searchA is the shared search results.
if(searchA.value.includes(searchB.value)) {
// A:`food` ∩ B:`foo` => `food`
return searchA;
}
if(searchB.value.includes(searchA.value)) {
// A:`foo` ∩ B:`food` => `food`
return searchB;
}
// A:`ice` ∩ B:`cream` => `ice` && `cream`
// But suppose AND isn't supported,
// So we return `UNDEFINABLE`.
return QueryLogic.UNDEFINABLE;
},
// Return a set that would load records in searchA that are not in
// searchB.
difference(searchA, searchB){
// if searchA's text contains searchB's text, then
// searchA has nothing outside what searchB would return.
if(searchA.value.includes(searchB.value)) {
// A:`food` \ B:`foo` => ∅
return QueryLogic.EMPTY;
}
// If searchA has results outside searchB's results
// then there are records, but we aren't able to
// create a string that represents this.
if(searchB.value.includes(searchA.value)) {
// A:`foo` \ B:`food` => UNDEFINABLE
return QueryLogic.UNDEFINABLE;
}
// A:`ice` \ B:`cream` => `ice` && !`cream`
// If there's another situation, we
// aren't able to express the difference
// so we return UNDEFINABLE.
return QueryLogic.UNDEFINABLE;
}
});
const recipes = [
{id: 1, name: "garlic chicken"},
{id: 2, name: "ice cream"},
{id: 3, name: "chicken kiev"}
];
const queryLogic = new QueryLogic({ keys: {
name : {[Symbol.for("can.SetType")]: SearchableStringSet}
}});
const result = queryLogic.filterMembers({
filter: {name: "chicken"}
}, recipes);
console.log( result ); //-> [
// {id: 1, name: "garlic chicken"},
// {id: 3, name: "chicken kiev"}
// ]
Notice how all values that match chicken
are returned.
import {canReflect, QueryLogic} from "can";
// Takes the value of `name` (ex: `"chicken"`)
function SearchableStringSet(value) {
this.value = value;
}
canReflect.assignSymbols(SearchableStringSet.prototype,{
// Returns if the name on a todo is actually a member of the set.
"can.isMember": function(value){
return value.includes(this.value);
},
// Converts back to a value that can be in a query.
"can.serialize": function(){
return this.value;
}
});
// Specify how to do the fundamental set comparisons.
QueryLogic.defineComparison(SearchableStringSet,SearchableStringSet,{
// Return a set that would load all records in searchA and searchB.
union(searchA, searchB){
// If searchA's text contains searchB's text, then
// searchB will include searchA's results.
if(searchA.value.includes(searchB.value)) {
// A:`food` ∪ B:`foo` => `foo`
return searchB;
}
if(searchB.value.includes(searchA.value)) {
// A:`foo` ∪ B:`food` => `foo`
return searchA;
}
// A:`ice` ∪ B:`cream` => `ice` || `cream`
return new QueryLogic.ValueOr([searchA, searchB]);
},
// Return a set that would load records shared by searchA and searchB.
intersection(searchA, searchB){
// If searchA's text contains searchB's text, then
// searchA is the shared search results.
if(searchA.value.includes(searchB.value)) {
// A:`food` ∩ B:`foo` => `food`
return searchA;
}
if(searchB.value.includes(searchA.value)) {
// A:`foo` ∩ B:`food` => `food`
return searchB;
}
// A:`ice` ∩ B:`cream` => `ice` && `cream`
// But suppose AND isn't supported,
// So we return `UNDEFINABLE`.
return QueryLogic.UNDEFINABLE;
},
// Return a set that would load records in searchA that are not in
// searchB.
difference(searchA, searchB){
// if searchA's text contains searchB's text, then
// searchA has nothing outside what searchB would return.
if(searchA.value.includes(searchB.value)) {
// A:`food` \ B:`foo` => ∅
return QueryLogic.EMPTY;
}
// If searchA has results outside searchB's results
// then there are records, but we aren't able to
// create a string that represents this.
if(searchB.value.includes(searchA.value)) {
// A:`foo` \ B:`food` => UNDEFINABLE
return QueryLogic.UNDEFINABLE;
}
// A:`ice` \ B:`cream` => `ice` && !`cream`
// If there's another situation, we
// aren't able to express the difference
// so we return UNDEFINABLE.
return QueryLogic.UNDEFINABLE;
}
});
const recipes = [
{id: 1, name: "garlic chicken"},
{id: 2, name: "ice cream"},
{id: 3, name: "chicken kiev"}
];
const queryLogic = new QueryLogic({ keys: {
name : {[Symbol.for("can.SetType")]: SearchableStringSet}
}});
const result = queryLogic.filterMembers({
filter: {name: "chicken"}
}, recipes);
console.log( result ); //-> [
// {id: 1, name: "garlic chicken"},
// {id: 3, name: "chicken kiev"}
// ]
Testing
To test SearchableStringSet
, you can use QueryLogic.set.union
, QueryLogic.set.intersection
and QueryLogic.set.difference
as follows:
test("SearchableStringSet", function(assert){
assert.deepEqual(
QueryLogic.set.union(
new SearchableStringSet("foo"),
new SearchableStringSet("food")
),
new SearchableStringSet("foo"),
"union works"
);
})