Skip to content

Filter nested objects #149

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 129 additions & 79 deletions taffy.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ var TAFFY, exports, T;
isIndexable, returnFilter, runFilters,
numcharsplit, orderByCol, run, intersection,
filter, makeCid, safeForJson,
isRegexp, sortArgs
isRegexp, sortArgs, lookup
;


Expand Down Expand Up @@ -241,10 +241,35 @@ var TAFFY, exports, T;
return match;
};

returnFilter = function ( f ) {
lookup = function ( o, ar ) {
// ****************************************
// *
// * Takes: filter object
// * Takes: an object and an array of string keys
// * Returns: the nested value in the object from looking up each key.
// * Failures currently result in an undefined value.
// * Purpose: Look up deeply nested values
// *
// ****************************************
if ( !T.isObject( o ) ){
return undefined;
}

var s = true;
each( ar, function ( v ) {
o = o[v];
if ( T.isUndefined( o ) ){
s = false;
return T.EXIT;
}
});

return s ? o : undefined;
};

returnFilter = function ( f, nq ) {
// ****************************************
// *
// * Takes: filter object and bool indicating whether to filter nested objects
// * Returns: a filter function
// * Purpose: Take a filter object and return a function that can be used to compare
// * a TaffyDB record to see if the record matches a query
Expand All @@ -258,7 +283,7 @@ var TAFFY, exports, T;

each( f, function ( r ) {
// loop the array and return a filter func for each value
nf.push( returnFilter( r ) );
nf.push( returnFilter( r, nq ) );
});
// now build a func to loop over the filters and return true if ANY of the filters match
// This handles logical OR expressions
Expand Down Expand Up @@ -303,85 +328,108 @@ var TAFFY, exports, T;
// loop over each test
looper( mtest, function ( mtest ) {

// su = match success
// f = match false
var su = true, f = false, matchFunc;
var f = false, matchFunc, innerFunc;


// push a function onto the filter collection to do the matching
matchFunc = function () {
// Wrapping the recursive function fixes scoping issues
// k = key array
innerFunc = function ( mtest, s, k ) {

// get the value from the record
var
// su = match success
su = true,
mvalue = lookup( this, k ),
eqeq = '==',
bangeq = '!=',
eqeqeq = '===',
lt = '<',
gt = '>',
lteq = '<=',
gteq = '>=',
bangeqeq = '!==',
r
;

if (typeof mvalue === 'undefined') {
return false;
}

// get the value from the record
var
mvalue = this[i],
eqeq = '==',
bangeq = '!=',
eqeqeq = '===',
lt = '<',
gt = '>',
lteq = '<=',
gteq = '>=',
bangeqeq = '!==',
r
;

if (typeof mvalue === 'undefined') {
return false;
}

if ( (s.indexOf( '!' ) === 0) && s !== bangeq &&
s !== bangeqeq )
{
// if the filter name starts with ! as in '!is' then reverse the match logic and remove the !
su = false;
s = s.substring( 1, s.length );
}
// get the match results based on the s/match type
/*jslint eqeq : true */
r = (
(s === 'regex') ? (mtest.test( mvalue )) : (s === 'lt' || s === lt)
? (mvalue < mtest) : (s === 'gt' || s === gt)
? (mvalue > mtest) : (s === 'lte' || s === lteq)
? (mvalue <= mtest) : (s === 'gte' || s === gteq)
? (mvalue >= mtest) : (s === 'left')
? (mvalue.indexOf( mtest ) === 0) : (s === 'leftnocase')
? (mvalue.toLowerCase().indexOf( mtest.toLowerCase() )
=== 0) : (s === 'right')
? (mvalue.substring( (mvalue.length - mtest.length) )
=== mtest) : (s === 'rightnocase')
? (mvalue.toLowerCase().substring(
(mvalue.length - mtest.length) ) === mtest.toLowerCase())
: (s === 'like')
? (mvalue.indexOf( mtest ) >= 0) : (s === 'likenocase')
? (mvalue.toLowerCase().indexOf(mtest.toLowerCase()) >= 0)
: (s === eqeqeq || s === 'is')
? (mvalue === mtest) : (s === eqeq)
? (mvalue == mtest) : (s === bangeqeq)
? (mvalue !== mtest) : (s === bangeq)
? (mvalue != mtest) : (s === 'isnocase')
? (mvalue.toLowerCase
? mvalue.toLowerCase() === mtest.toLowerCase()
: mvalue === mtest) : (s === 'has')
? (T.has( mvalue, mtest )) : (s === 'hasall')
? (T.hasAll( mvalue, mtest )) : (s === 'contains')
? (TAFFY.isArray(mvalue) && mvalue.indexOf(mtest) > -1) : (
s.indexOf( 'is' ) === -1
&& !TAFFY.isNull( mvalue )
&& !TAFFY.isUndefined( mvalue )
&& !TAFFY.isObject( mtest )
&& !TAFFY.isArray( mtest )
)
? (mtest === mvalue[s])
: (T[s] && T.isFunction( T[s] )
&& s.indexOf( 'is' ) === 0)
? T[s]( mvalue ) === mtest
: (T[s] && T.isFunction( T[s] ))
? T[s]( mvalue, mtest ) : (false)
);
/*jslint eqeq : false */
r = (r && !su) ? false : (!r && !su) ? true : r;

return r;
if ( (s.indexOf( '!' ) === 0) && s !== bangeq &&
s !== bangeqeq )
{
// if the filter name starts with ! as in '!is' then reverse the match logic and remove the !
su = false;
s = s.substring( 1, s.length );
}
// get the match results based on the s/match type
/*jslint eqeq : true */
r = (
(s === 'regex') ? (mtest.test( mvalue )) : (s === 'lt' || s === lt)
? (mvalue < mtest) : (s === 'gt' || s === gt)
? (mvalue > mtest) : (s === 'lte' || s === lteq)
? (mvalue <= mtest) : (s === 'gte' || s === gteq)
? (mvalue >= mtest) : (s === 'left')
? (mvalue.indexOf( mtest ) === 0) : (s === 'leftnocase')
? (mvalue.toLowerCase().indexOf( mtest.toLowerCase() )
=== 0) : (s === 'right')
? (mvalue.substring( (mvalue.length - mtest.length) )
=== mtest) : (s === 'rightnocase')
? (mvalue.toLowerCase().substring(
(mvalue.length - mtest.length) ) === mtest.toLowerCase())
: (s === 'like')
? (mvalue.indexOf( mtest ) >= 0) : (s === 'likenocase')
? (mvalue.toLowerCase().indexOf(mtest.toLowerCase()) >= 0)
: (s === eqeqeq || s === 'is')
? (mvalue === mtest) : (s === eqeq)
? (mvalue == mtest) : (s === bangeqeq)
? (mvalue !== mtest) : (s === bangeq)
? (mvalue != mtest) : (s === 'isnocase')
? (mvalue.toLowerCase
? mvalue.toLowerCase() === mtest.toLowerCase()
: mvalue === mtest) : (s === 'has')
? (T.has( mvalue, mtest )) : (s === 'hasall')
? (T.hasAll( mvalue, mtest )) : (s === 'contains')
? (TAFFY.isArray(mvalue) && mvalue.indexOf(mtest) > -1) : (
s.indexOf( 'is' ) === -1
&& !TAFFY.isNull( mvalue )
&& !TAFFY.isUndefined( mvalue )
&& !TAFFY.isObject( mtest )
&& !TAFFY.isArray( mtest )
)
? (mtest === mvalue[s])
: (T[s] && T.isFunction( T[s] )
&& s.indexOf( 'is' ) === 0)
? T[s]( mvalue ) === mtest
: (T[s] && T.isFunction( T[s] ))
? T[s]( mvalue, mtest ) : (T.isArray( mtest ))
// Logical OR
? (T.has( mtest, mvalue[s] )) : (false)
);

if ( nq && !r && T.isObject( mtest ) ){
r = true;
var that = this;

// Recursively match nested objects
eachin( mtest, function ( mtest, t ) {
if ( !innerFunc.call( that, mtest, t, k.concat(s) ) ){
r = false;
return T.EXIT;
}
});
}

/*jslint eqeq : false */
r = (r && !su) ? false : (!r && !su) ? true : r;

return r;
};

return innerFunc.call( this, mtest, s, [i] );
};
c.push( matchFunc );

Expand Down Expand Up @@ -637,9 +685,10 @@ var TAFFY, exports, T;
nq.push( v );
});
nc.q = nq;
var that = this;
// Hadnle passing of ___ID or a record on lookup.
each( sortArgs(arguments), function ( f ) {
nc.q.push( returnFilter( f ) );
nc.q.push( returnFilter( f, that.context().settings.nested ) );
nc.filterRaw.push( f );
});

Expand Down Expand Up @@ -1190,6 +1239,7 @@ var TAFFY, exports, T;
onRemove : false,
onDBChange : false,
storageName : false,
nested : false,
forcePropertyCase : null,
cacheSize : 100,
name : ''
Expand Down Expand Up @@ -1611,7 +1661,7 @@ var TAFFY, exports, T;
context.index.push( f );
}
else {
context.q.push( returnFilter( f ) );
context.q.push( returnFilter( f, context.settings.nested ) );
}
context.filterRaw.push( f );
});
Expand Down
Loading