Permalink
Browse files

Added a "$computed" operator that allows queries to be performed on c…

…omputed properties
  • Loading branch information...
davidgtonge committed Mar 2, 2012
1 parent 6fd8361 commit 618509eae7468c774bbfb52a74ae23e9c4841114
Showing with 183 additions and 30 deletions.
  1. +36 −0 README.md
  2. +43 −16 js/backbone-query.js
  3. +1 −1 js/backbone-query.min.js
  4. +23 −10 src/backbone-query.coffee
  5. +28 −1 test/backbone-query-test.coffee
  6. +52 −2 test/backbone-query-test.js
View
@@ -277,6 +277,42 @@ Posts.query({
All of the operators above can be performed on `$elemMatch` queries, e.g. `$all`, `$size` or `$lt`.
+### $computed
+This operator allows you to perform queries on computed properties. For example you may want to perform a query
+for a persons full name, even though the first and last name are stored separately in your db / model.
+For example
+
+```js
+testModel = Backbone.Model.extend({
+ full_name: function() {
+ return (this.get('first_name')) + " " + (this.get('last_name'));
+ }
+});
+
+a = new testModel({
+ first_name: "Dave",
+ last_name: "Tonge"
+});
+
+b = new testModel({
+ first_name: "John",
+ last_name: "Smith"
+});
+
+MyCollection = new QueryCollection([a, b]);
+
+MyCollection.query({
+ full_name: { $computed: "Dave Tonge" }
+});
+// Returns the model with the computed `full_name` equal to Dave Tonge
+
+MyCollection.query({
+ full_name: { $computed: { $likeI: "john smi" } }
+});
+// Any of the previous operators can be used (including elemMatch is required)
+```
+
+
Combined Queries
================
View
@@ -6,11 +6,11 @@ May be freely distributed according to MIT license.
*/
(function() {
- var filter, get_cache, get_models, get_sorted_models, iterator, page_models, parse_query, perform_query, process_query, reject, sort_models, test_model_attribute, test_query_value,
+ var detect, filter, get_cache, get_models, get_sorted_models, iterator, page_models, parse_query, perform_query, process_query, reject, sort_models, test_model_attribute, 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; };
parse_query = function(raw_query) {
- var key, o, query_param, type, value, _results;
+ var key, o, q, query_param, type, value, _results;
_results = [];
for (key in raw_query) {
query_param = raw_query[key];
@@ -25,10 +25,17 @@ May be freely distributed according to MIT license.
value = query_param[type];
if (test_query_value(type, value)) {
o.type = type;
- if (type === "$elemMatch") {
- o.value = parse_query(value);
- } else {
- o.value = value;
+ switch (type) {
+ case "$elemMatch":
+ o.value = parse_query(value);
+ break;
+ case "$computed":
+ q = {};
+ q[key] = value;
+ o.value = parse_query(q);
+ break;
+ default:
+ o.value = value;
}
}
}
@@ -86,7 +93,7 @@ May be freely distributed according to MIT license.
}
};
- perform_query = function(type, value, attr, model) {
+ perform_query = function(type, value, attr, model, key) {
switch (type) {
case "$equal":
if (_(attr).isArray()) {
@@ -137,7 +144,9 @@ May be freely distributed according to MIT license.
case "$cb":
return value.call(model, attr);
case "$elemMatch":
- return (iterator(attr, value, false, filter, true)).length > 0;
+ return iterator(attr, value, false, detect, "elemMatch");
+ case "$computed":
+ return iterator([model], value, false, detect, "computed");
default:
return false;
}
@@ -151,35 +160,53 @@ May be freely distributed according to MIT license.
var attr, q, test, _i, _len;
for (_i = 0, _len = parsed_query.length; _i < _len; _i++) {
q = parsed_query[_i];
- attr = subQuery ? model[q.key] : model.get(q.key);
+ attr = (function() {
+ switch (subQuery) {
+ case "elemMatch":
+ return model[q.key];
+ case "computed":
+ return model[q.key]();
+ default:
+ return model.get(q.key);
+ }
+ })();
test = test_model_attribute(q.type, attr);
- if (test) test = perform_query(q.type, q.value, attr, model);
+ if (test) test = perform_query(q.type, q.value, attr, model, q.key);
if (andOr === test) return andOr;
}
return !andOr;
});
};
filter = function(array, test) {
- var index, val, _i, _len, _results;
+ var val, _i, _len, _results;
_results = [];
- for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) {
- val = array[index];
+ for (_i = 0, _len = array.length; _i < _len; _i++) {
+ val = array[_i];
if (test(val)) _results.push(val);
}
return _results;
};
reject = function(array, test) {
- var index, val, _i, _len, _results;
+ var val, _i, _len, _results;
_results = [];
- for (index = _i = 0, _len = array.length; _i < _len; index = ++_i) {
- val = array[index];
+ for (_i = 0, _len = array.length; _i < _len; _i++) {
+ val = array[_i];
if (!test(val)) _results.push(val);
}
return _results;
};
+ detect = function(array, test) {
+ var val, _i, _len;
+ for (_i = 0, _len = array.length; _i < _len; _i++) {
+ val = array[_i];
+ if (test(val)) return true;
+ }
+ return false;
+ };
+
process_query = {
$and: function(models, query) {
return iterator(models, query, false, filter);
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
View
@@ -19,10 +19,15 @@ parse_query = (raw_query) ->
# Before adding the query, its value is checked to make sure it is the right type
if test_query_value type, value
o.type = type
- if type is "$elemMatch"
- o.value = parse_query value
- else
- o.value = value
+ switch type
+ when "$elemMatch"
+ o.value = parse_query value
+ when "$computed"
+ q = {}
+ q[key] = value
+ o.value = parse_query q
+ else
+ o.value = value
# If the query_param is not an object or a regexp then revert to the default operator: $equal
else
o.type = "$equal"
@@ -53,7 +58,7 @@ test_model_attribute = (type, value) ->
else true
# Perform the actual query logic for each query and each model/attribute
-perform_query = (type, value, attr, model) ->
+perform_query = (type, value, attr, model, key) ->
switch type
when "$equal"
# If the attrubute is an array then search for the query value in the array the same as Mongo
@@ -76,7 +81,8 @@ perform_query = (type, value, attr, model) ->
when "$likeI" then attr.toLowerCase().indexOf(value.toLowerCase()) isnt -1
when "$regex" then value.test attr
when "$cb" then value.call model, attr
- when "$elemMatch" then (iterator attr, value, false, filter, true).length > 0
+ when "$elemMatch" then iterator attr, value, false, detect, "elemMatch"
+ when "$computed" then iterator [model], value, false, detect, "computed"
else false
@@ -88,11 +94,14 @@ iterator = (models, query, andOr, filterReject, subQuery = false) ->
# For each model in the collection, iterate through the supplied queries
for q in parsed_query
# Retrieve the attribute value from the model
- attr = if subQuery then model[q.key] else model.get(q.key)
+ attr = switch subQuery
+ when "elemMatch" then model[q.key]
+ when "computed" then model[q.key]()
+ else model.get(q.key)
# Check if the attribute value is the right type (some operators need a string, or an array)
test = test_model_attribute(q.type, attr)
# If the attribute test is true, perform the query
- if test then test = perform_query q.type, q.value, attr, model
+ if test then test = perform_query q.type, q.value, attr, model, q.key
# 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 test
@@ -103,8 +112,12 @@ iterator = (models, query, andOr, filterReject, subQuery = false) ->
# Custom Filter / Reject methods faster than underscore methods as use for loops
# http://jsperf.com/filter-vs-for-loop2
-filter = (array, test) -> (val for val, index in array when test val)
-reject = (array, test) -> (val for val, index in array when not test val)
+filter = (array, test) -> (val for val in array when test val)
+reject = (array, test) -> (val for val in array when not test val)
+detect = (array, test) ->
+ for val in array
+ return true if test val
+ false
# An object with or, and, nor and not methods
process_query =
@@ -1,7 +1,10 @@
+
if typeof require isnt "undefined"
{QueryCollection} = require "../js/backbone-query.js"
+ Backbone = require "backbone"
else
- QueryCollection = Backbone.QueryCollection
+ QueryCollection = window.Backbone.QueryCollection
+ Backbone = window.Backbone
# Helper functions that turn Qunit tests into nodeunit tests
equals = []
@@ -345,6 +348,30 @@ test "Where method", ->
equal result.models.length, result.length
+test "$computed", ->
+ class testModel extends Backbone.Model
+ full_name: -> "#{@get 'first_name'} #{@get 'last_name'}"
+
+ a = new testModel
+ first_name: "Dave"
+ last_name: "Tonge"
+ b = new testModel
+ first_name: "John"
+ last_name: "Smith"
+ c = new QueryCollection [a,b]
+
+ result = c.query
+ full_name: $computed: "Dave Tonge"
+
+ equal result.length, 1
+ equal result[0].get("first_name"), "Dave"
+
+ result = c.query
+ full_name: $computed: $likeI: "n sm"
+ equal result.length, 1
+ equal result[0].get("first_name"), "John"
+
+
test "$elemMatch", ->
a = new QueryCollection [
{title: "Home", comments:[

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.

0 comments on commit 618509e

Please sign in to comment.