Browse files

Added method to test that attribute is of the correct type

For example $like and $regex operators will now only be run if the attribute value is a string.
Added tests to check operators with undefined values
  • Loading branch information...
1 parent d215c18 commit d94b14f058a1967958961a99d5a0508d35eb0b54 @davidgtonge committed Jan 26, 2012
Showing with 167 additions and 80 deletions.
  1. +61 −51 js/backbone-query.js
  2. +1 −1 js/backbone-query.min.js
  3. +32 −26 src/backbone-query.coffee
  4. +19 −1 test/backbone-query-test.coffee
  5. +54 −1 test/backbone-query-test.js
View
112 js/backbone-query.js
@@ -6,7 +6,7 @@ May be freely distributed according to MIT license.
*/
(function() {
- var and_iterator, array_intersection, get_cache, get_models, get_sorted_models, iterator, or_iterator, page_models, parse_query, process_query, sort_models, test_query_value,
+ var and_iterator, array_intersection, get_cache, get_models, get_sorted_models, iterator, or_iterator, page_models, parse_query, process_query, sort_models, test_attr, test_query_value,
__indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
array_intersection = function(arrays) {
@@ -69,71 +69,81 @@ May be freely distributed according to MIT license.
}
};
+ test_attr = function(type, value) {
+ switch (type) {
+ case "$like":
+ case "$regex":
+ return _(value).isString();
+ case "$contains":
+ case "$all":
+ case "$any":
+ return _(value).isArray();
+ case "$size":
+ return _(value).isArray() || _(value).isString();
+ case "$in":
+ case "$nin":
+ return value != null;
+ default:
+ return true;
+ }
+ };
+
iterator = function(collection, query, andOr) {
var parsed_query;
parsed_query = parse_query(query);
return collection.filter(function(model) {
- var attr, q, _i, _len;
+ var attr, q, test, _i, _len;
for (_i = 0, _len = parsed_query.length; _i < _len; _i++) {
q = parsed_query[_i];
attr = model.get(q.key);
- if (andOr === ((function() {
- var _ref;
- switch (q.type) {
- case "$equal":
- return attr === q.value;
- case "$contains":
- if (_(attr).isArray()) {
+ test = test_attr(q.type, attr);
+ if (test) {
+ test = ((function() {
+ var _ref;
+ switch (q.type) {
+ case "$equal":
+ return attr === q.value;
+ case "$contains":
return _ref = q.value, __indexOf.call(attr, _ref) >= 0;
- } else {
- return false;
- }
- break;
- case "$ne":
- return attr !== q.value;
- case "$lt":
- return attr < q.value;
- case "$gt":
- return attr > q.value;
- case "$lte":
- return attr <= q.value;
- case "$gte":
- return attr >= q.value;
- case "$between":
- return (q.value[0] < attr && attr < q.value[1]);
- case "$in":
- return __indexOf.call(q.value, attr) >= 0;
- case "$nin":
- return __indexOf.call(q.value, attr) < 0;
- case "$all":
- if (_(attr).isArray()) {
+ case "$ne":
+ return attr !== q.value;
+ case "$lt":
+ return attr < q.value;
+ case "$gt":
+ return attr > q.value;
+ case "$lte":
+ return attr <= q.value;
+ case "$gte":
+ return attr >= q.value;
+ case "$between":
+ return (q.value[0] < attr && attr < q.value[1]);
+ case "$in":
+ return __indexOf.call(q.value, attr) >= 0;
+ case "$nin":
+ return __indexOf.call(q.value, attr) < 0;
+ case "$all":
return _(model.get(q.key)).all(function(item) {
return __indexOf.call(q.value, item) >= 0;
});
- }
- break;
- case "$any":
- if (_(attr).isArray()) {
+ case "$any":
return _(model.get(q.key)).any(function(item) {
return __indexOf.call(q.value, item) >= 0;
});
- }
- break;
- case "$size":
- return attr.length === q.value;
- case "$exists":
- case "$has":
- return model.has(q.key) === q.value;
- case "$like":
- return attr.indexOf(q.value) !== -1;
- case "$regex":
- return q.value.test(attr);
- case "$cb":
- return q.value.call(model, attr);
- }
- })())) {
- return andOr;
+ case "$size":
+ return attr.length === q.value;
+ case "$exists":
+ case "$has":
+ return model.has(q.key) === q.value;
+ case "$like":
+ return attr.indexOf(q.value) !== -1;
+ case "$regex":
+ return q.value.test(attr);
+ case "$cb":
+ return q.value.call(model, attr);
+ }
+ })());
}
+ if (andOr === test) return andOr;
}
return !andOr;
});
View
2 js/backbone-query.min.js
@@ -1 +1 @@
-((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};b=function(a){var b;return b=_.rest(a),_.filter(_.uniq(a[0]),function(a){return _.every(b,function(b){return _.indexOf(b,a)>=0})})},i=function(a){var b,c,d,e,f,g;g=[];for(b in a){d=a[b],c={key:b};if(_.isRegExp(d))c.type="$regex",c.value=d;else if(_(d).isObject())for(e in d)f=d[e],l(e,f)&&(c.type=e,c.value=f);else c.type="$equal",c.value=d;g.push(c)}return g},l=function(a,b){switch(a){case"$in":case"$nin":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isNumber();case"$regex":return _(b).isRegExp();case"$like":return _(b).isString();case"$between":return _(b).isArray()&&b.length===2;case"$cb":return _(b).isFunction();default:return!0}},f=function(a,b,c){var d;return d=i(b),a.filter(function(a){var b,e,f,g;for(f=0,g=d.length;f<g;f++){e=d[f],b=a.get(e.key);if(c===function(){var c;switch(e.type){case"$equal":return b===e.value;case"$contains":return _(b).isArray()?(c=e.value,m.call(b,c)>=0):!1;case"$ne":return b!==e.value;case"$lt":return b<e.value;case"$gt":return b>e.value;case"$lte":return b<=e.value;case"$gte":return b>=e.value;case"$between":return e.value[0]<b&&b<e.value[1];case"$in":return m.call(e.value,b)>=0;case"$nin":return m.call(e.value,b)<0;case"$all":if(_(b).isArray())return _(a.get(e.key)).all(function(a){return m.call(e.value,a)>=0});break;case"$any":if(_(b).isArray())return _(a.get(e.key)).any(function(a){return m.call(e.value,a)>=0});break;case"$size":return b.length===e.value;case"$exists":case"$has":return a.has(e.key)===e.value;case"$like":return b.indexOf(e.value)!==-1;case"$regex":return e.value.test(b);case"$cb":return e.value.call(a,b)}}())return c}return!c})},a=function(a,b){return f(a,b,!1)},g=function(a,b){return f(a,b,!0)},j={$and:function(b,c){return a(b,c)},$or:function(a,b){return g(a,b)},$nor:function(a,b){return _.difference(a.models,g(a,b))},$not:function(b,c){return _.difference(b.models,a(b,c))}},c=function(a,b,c){var d,f,g,h;return g=JSON.stringify(b),d=(h=a._query_cache)!=null?h:a._query_cache={},f=d[g],f||(f=e(a,b,c),d[g]=f),f},d=function(a,c){var d,e,f;d=_(c).chain().keys().intersection(["$or","$and","$nor","$not"]).value();switch(d.length){case 0:return j.$and(a,c);case 1:return f=d[0],j[f](a,c[f]);default:return e=function(){var b,e,g;g=[];for(b=0,e=d.length;b<e;b++)f=d[b],g.push(j[f](a,c[f]));return g}(),b(e)}},e=function(a,b,c){var e;return e=d(a,b),c.sortBy&&(e=k(e,c)),e},k=function(a,b){return _(b.sortBy).isString()?a=_(a).sortBy(function(a){return a.get(b.sortBy)}):_(b.sortBy).isFunction()&&(a=_(a).sortBy(b.sortBy)),b.order==="desc"&&(a=a.reverse()),a},h=function(a,b){var c,d,e,f;return b.offset?e=b.offset:b.page?e=(b.page-1)*b.limit:e=0,c=e+b.limit,d=a.slice(e,c),b.pager&&_.isFunction(b.pager)&&(f=Math.ceil(a.length/b.limit),b.pager(f,d)),d},Backbone.QueryCollection=Backbone.Collection.extend({query:function(a,b){var d;return b==null&&(b={}),b.cache?d=c(this,a,b):d=e(this,a,b),b.limit&&(d=h(d,b)),d},reset_query_cache:function(){return this._query_cache={}}})})).call(this)
+((function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n=Array.prototype.indexOf||function(a){for(var b=0,c=this.length;b<c;b++)if(b in this&&this[b]===a)return b;return-1};b=function(a){var b;return b=_.rest(a),_.filter(_.uniq(a[0]),function(a){return _.every(b,function(b){return _.indexOf(b,a)>=0})})},i=function(a){var b,c,d,e,f,g;g=[];for(b in a){d=a[b],c={key:b};if(_.isRegExp(d))c.type="$regex",c.value=d;else if(_(d).isObject())for(e in d)f=d[e],m(e,f)&&(c.type=e,c.value=f);else c.type="$equal",c.value=d;g.push(c)}return g},m=function(a,b){switch(a){case"$in":case"$nin":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isNumber();case"$regex":return _(b).isRegExp();case"$like":return _(b).isString();case"$between":return _(b).isArray()&&b.length===2;case"$cb":return _(b).isFunction();default:return!0}},l=function(a,b){switch(a){case"$like":case"$regex":return _(b).isString();case"$contains":case"$all":case"$any":return _(b).isArray();case"$size":return _(b).isArray()||_(b).isString();case"$in":case"$nin":return b!=null;default:return!0}},f=function(a,b,c){var d;return d=i(b),a.filter(function(a){var b,e,f,g,h;for(g=0,h=d.length;g<h;g++){e=d[g],b=a.get(e.key),f=l(e.type,b),f&&(f=function(){var c;switch(e.type){case"$equal":return b===e.value;case"$contains":return c=e.value,n.call(b,c)>=0;case"$ne":return b!==e.value;case"$lt":return b<e.value;case"$gt":return b>e.value;case"$lte":return b<=e.value;case"$gte":return b>=e.value;case"$between":return e.value[0]<b&&b<e.value[1];case"$in":return n.call(e.value,b)>=0;case"$nin":return n.call(e.value,b)<0;case"$all":return _(a.get(e.key)).all(function(a){return n.call(e.value,a)>=0});case"$any":return _(a.get(e.key)).any(function(a){return n.call(e.value,a)>=0});case"$size":return b.length===e.value;case"$exists":case"$has":return a.has(e.key)===e.value;case"$like":return b.indexOf(e.value)!==-1;case"$regex":return e.value.test(b);case"$cb":return e.value.call(a,b)}}());if(c===f)return c}return!c})},a=function(a,b){return f(a,b,!1)},g=function(a,b){return f(a,b,!0)},j={$and:function(b,c){return a(b,c)},$or:function(a,b){return g(a,b)},$nor:function(a,b){return _.difference(a.models,g(a,b))},$not:function(b,c){return _.difference(b.models,a(b,c))}},c=function(a,b,c){var d,f,g,h;return g=JSON.stringify(b),d=(h=a._query_cache)!=null?h:a._query_cache={},f=d[g],f||(f=e(a,b,c),d[g]=f),f},d=function(a,c){var d,e,f;d=_(c).chain().keys().intersection(["$or","$and","$nor","$not"]).value();switch(d.length){case 0:return j.$and(a,c);case 1:return f=d[0],j[f](a,c[f]);default:return e=function(){var b,e,g;g=[];for(b=0,e=d.length;b<e;b++)f=d[b],g.push(j[f](a,c[f]));return g}(),b(e)}},e=function(a,b,c){var e;return e=d(a,b),c.sortBy&&(e=k(e,c)),e},k=function(a,b){return _(b.sortBy).isString()?a=_(a).sortBy(function(a){return a.get(b.sortBy)}):_(b.sortBy).isFunction()&&(a=_(a).sortBy(b.sortBy)),b.order==="desc"&&(a=a.reverse()),a},h=function(a,b){var c,d,e,f;return b.offset?e=b.offset:b.page?e=(b.page-1)*b.limit:e=0,c=e+b.limit,d=a.slice(e,c),b.pager&&_.isFunction(b.pager)&&(f=Math.ceil(a.length/b.limit),b.pager(f,d)),d},Backbone.QueryCollection=Backbone.Collection.extend({query:function(a,b){var d;return b==null&&(b={}),b.cache?d=c(this,a,b):d=e(this,a,b),b.limit&&(d=h(d,b)),d},reset_query_cache:function(){return this._query_cache={}}})})).call(this)
View
58 src/backbone-query.coffee
@@ -48,42 +48,48 @@ test_query_value = (type, value) ->
when "$cb" then _(value).isFunction()
else true
+test_attr = (type, value) ->
+ switch type
+ when "$like", "$regex" then _(value).isString()
+ when "$contains", "$all", "$any" then _(value).isArray()
+ when "$size" then _(value).isArray() or _(value).isString()
+ when "$in", "$nin" then value?
+ else true
+
# The main iterator that actually applies the query
iterator = (collection, query, andOr) ->
parsed_query = parse_query query
# The collections filter method is used to iterate through each model in the collection
collection.filter (model) ->
# For each model in the collection, iterate through the supplied queries
for q in parsed_query
+ # Retrieve the attribute value from the model
attr = model.get(q.key)
+ # Check if the attribute value is the right type (some operators need a string, or an array)
+ test = test_attr(q.type, attr)
+ if test then test = (switch q.type
+ when "$equal" then attr is q.value
+ when "$contains" then q.value in attr
+ when "$ne" then attr isnt q.value
+ when "$lt" then attr < q.value
+ when "$gt" then attr > q.value
+ when "$lte" then attr <= q.value
+ when "$gte" then attr >= q.value
+ when "$between" then q.value[0] < attr < q.value[1]
+ when "$in" then attr in q.value
+ when "$nin" then attr not in q.value
+ when "$all" then _(model.get q.key).all (item) -> item in q.value
+ when "$any" then _(model.get q.key).any (item) -> item in q.value
+ when "$size" then attr.length is q.value
+ when "$exists", "$has" then model.has(q.key) is q.value
+ when "$like" then attr.indexOf(q.value) isnt -1
+ when "$regex" then q.value.test attr
+ when "$cb" then q.value.call model, attr)
+
# If the query is an "or" query than as soon as a match is found we return "true"
# Whereas if the query is an "and" query then we return "false" as soon as a match isn't found.
- return andOr if andOr is (switch q.type
- when "$equal" then attr is q.value
- when "$contains"
- #For this method the model attribute is confirmed to be an array before looping through it
- if _(attr).isArray() then (q.value in attr) else false
- when "$ne" then attr isnt q.value
- when "$lt" then attr < q.value
- when "$gt" then attr > q.value
- when "$lte" then attr <= q.value
- when "$gte" then attr >= q.value
- when "$between" then q.value[0] < attr < q.value[1]
- when "$in" then attr in q.value
- when "$nin" then attr not in q.value
- when "$all"
- #For this method the model attribute is confirmed to be an array before looping through it
- if _(attr).isArray()
- _(model.get q.key).all (item) -> item in q.value
- when "$any"
- #For this method the model attribute is confirmed to be an array before looping through it
- if _(attr).isArray()
- _(model.get q.key).any (item) -> item in q.value
- when "$size" then attr.length is q.value
- when "$exists", "$has" then model.has(q.key) is q.value
- when "$like" then attr.indexOf(q.value) isnt -1
- when "$regex" then q.value.test attr
- when "$cb" then q.value.call model, attr)
+ return andOr if andOr is test
+
# For an "or" query, if all the queries are false, then we return false
# For an "and" query, if all the queries are true, then we return true
not andOr
View
20 test/backbone-query-test.coffee
@@ -86,7 +86,7 @@ test "$all operator", ->
test "$all operator (wrong values)", ->
a = create()
result = a.query title: {$all: ["red","blue"]}
- equal result.length, 3
+ equal result.length, 0
result = a.query colors: {$all: "red"}
equal result.length, 3
@@ -285,6 +285,24 @@ test "cache with multiple collections", ->
equal b.length, 1
+test "null attribute with various operators", ->
+ a = create()
+ result = a.query wrong_key: {$like: "test"}
+ equal result.length, 0
+ result = a.query wrong_key: {$regex: /test/}
+ equal result.length, 0
+ result = a.query wrong_key: {$contains: "test"}
+ equal result.length, 0
+ result = a.query wrong_key: {$all: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$any: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$size: 10}
+ equal result.length, 0
+ result = a.query wrong_key: {$in: [12,23]}
+ equal result.length, 0
+ result = a.query wrong_key: {$nin: [12,23]}
+ equal result.length, 0
View
55 test/backbone-query-test.js
@@ -190,7 +190,7 @@
$all: ["red", "blue"]
}
});
- equal(result.length, 3);
+ equal(result.length, 0);
result = a.query({
colors: {
$all: "red"
@@ -676,4 +676,57 @@
return equal(b.length, 1);
});
+ test("null attribute with various operators", function() {
+ var a, result;
+ a = create();
+ result = a.query({
+ wrong_key: {
+ $like: "test"
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $regex: /test/
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $contains: "test"
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $all: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $any: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $size: 10
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $in: [12, 23]
+ }
+ });
+ equal(result.length, 0);
+ result = a.query({
+ wrong_key: {
+ $nin: [12, 23]
+ }
+ });
+ return equal(result.length, 0);
+ });
+
}).call(this);

0 comments on commit d94b14f

Please sign in to comment.