Skip to content

Commit

Permalink
Added method to test that attribute is of the correct type
Browse files Browse the repository at this point in the history
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
davidgtonge committed Jan 26, 2012
1 parent d215c18 commit d94b14f
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 80 deletions.
112 changes: 61 additions & 51 deletions js/backbone-query.js
Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
});
Expand Down
2 changes: 1 addition & 1 deletion js/backbone-query.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 32 additions & 26 deletions src/backbone-query.coffee
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion test/backbone-query-test.coffee
Expand Up @@ -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
Expand Down Expand Up @@ -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



Expand Down
55 changes: 54 additions & 1 deletion test/backbone-query-test.js
Expand Up @@ -190,7 +190,7 @@
$all: ["red", "blue"]
}
});
equal(result.length, 3);
equal(result.length, 0);
result = a.query({
colors: {
$all: "red"
Expand Down Expand Up @@ -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.