node-dirty-query
A lightweight utility for querying "node dirty" databases.
Adds the ability to search for models with a Query API similar to
MongoDB
Please report any bugs, feature requests in the issue tracker.
Pull requests are welcome!
Usage
You can install with NPM: npm install dirty-query
Then simply require in your project: query = require("dirty-query").query
Use the query method like this:
db = require('dirty')('my-db')
query db, { {featured:true}, {likes: $gt:10} };
Query API (NEED TO UPDATE - CURRENTLY DESCRIBES BACKBONE QUERY)
===
### $equal
Performs a strict equality test using `===`. If no operator is provided and the query value isn't a regex then `$equal` is assumed.
```javascript
MyCollection.query({ title:"Test" });
// Returns all models which have a "title" attribute of "Test"
MyCollection.query({ title: {$equal:"Test"} }); // Same as above
$contains
Assumes that the model property is an array and searches for the query value in the array
MyCollection.query({ colors: {$contains: "red"} });
$ne
"Not equal", the opposite of $equal, returns all models which don't have the query value
MyCollection.query({ title: {$ne:"Test"} });
$lt, $lte, $gt, $gte
These conditional operators can be used for greater than and less than comparisons in queries
MyCollection.query({ likes: {$lt:10} });
MyCollection.query({ likes: {$lte:10} });
MyCollection.query({ likes: {$gt:10} });
MyCollection.query({ likes: {$gte:10} });
$between
To check if a value is in-between 2 query values use the $between operator and supply an array with the min and max value
MyCollection.query({ likes: {$between:[5,15} });
$in
An array of possible values can be supplied using $in, a model will be returned if any of the supplied values is matched
MyCollection.query({ title: {$in:["About", "Home", "Contact"] } });
$nin
"Not in", the opposite of $in. A model will be returned if none of the supplied values is matched
MyCollection.query({ title: {$nin:["About", "Home", "Contact"] } });
$all
Assumes the model property is an array and only returns models where all supplied values are matched.
MyCollection.query({ colors: {$all:["red", "yellow"] } });
$any
Assumes the model property is an array and returns models where any of the supplied values are matched.
MyCollection.query({ colors: {$any:["red", "yellow"] } });
$size
Assumes the model property has a length (i.e. is either an array or a string).
Only returns models the model property's length matches the supplied values
MyCollection.query({ colors: {$size:2 } });
$exists or $has
Checks for the existence of an attribute. Can be supplied either true or false.
MyCollection.query({ title: {$exists: true } });
MyCollection.query({ title: {$has: false } });
$like
Assumes the model attribute is a string and checks if the supplied query value is a substring of the property.
Uses indexOf rather than regex for performance reasons
MyCollection.query({ title: {$like: "Test" } });
$likeI
The same as above but performs a case insensitive search using indexOf and toLowerCase (still faster than Regex)
MyCollection.query({ title: {$likeI: "Test" } });
$regex
Checks if the model attribute matches the supplied regular expression. The regex query can be supplied without the $regex
keyword
MyCollection.query({ content: {$regex: /coffeescript/gi } });
MyCollection.query({ content: /coffeescript/gi });
$cb
A callback function can be supplied as a test. The callback will receive the attribute and should return either true or false.
this
will be set to the current model, this can help with tests against computed properties
MyCollection.query({ title: {$cb: function(attr){ return attr.charAt(0) === "c";}} });
MyCollection.query({ computed_test: {$cb: function(){ return this.computed_property() > 10;}} });
For callbacks that use this
rather than the model attribute, the key name supplied is arbitrary and has no
effect on the results. If the only test you were performing was like the above test it would make more sense
to simply use MyCollection.filter
. However if you are performing other tests or are using the paging / sorting /
caching options of backbone query, then this functionality is useful.
Combined Queries
Multiple queries can be combined together. By default all supplied queries must be matched $and
. However it is possible
to specify either $or
, $nor
, $not
to implement alternate logic.
$and
MyCollection.query({ $and: { title: {$like: "News"}, likes: {$gt: 10}}});
MyCollection.query({ title: {$like: "News"}, likes: {$gt: 10} });
$or
MyCollection.query({ $or: { title: {$like: "News"}, likes: {$gt: 10}}});
$nor
The opposite of $or
MyCollection.query({ $nor: { title: {$like: "News"}, likes: {$gt: 10}}});
$not
The opposite of $and
MyCollection.query({ $not: { title: {$like: "News"}, likes: {$gt: 10}}});
Compound Queries
It is possible to use multiple combined queries, for example searching for models that have a specific title attribute,
and either a category of "abc" or a tag of "xyz"
MyCollection.query({
$and: { title: {$like: "News"}},
$or: {likes: {$gt: 10}, color:{$contains:"red"}}
});
Sorting
Optional sortBy
and order
attributes can be supplied as part of an options object.
sortBy
can either be a model key or a callback function which will be called with each model in the array.
MyCollection.query({title: {$like: "News"}}, {sortBy: "likes"});
MyCollection.query({title: {$like: "News"}}, {sortBy: "likes", order:"desc"});
MyCollection.query(
{title: {$like: "News"}},
{sortBy: function(model){ return model.get("title").charAt(1);}}
);
Paging
To return only a subset of the results paging properties can be supplied as part of an options object.
A limit
property must be supplied and optionally a offset
or a page
property can be supplied.
MyCollection.query({likes:{$gt:10}}, {limit:10});
MyCollection.query({likes:{$gt:10}}, {limit:10, offset:5});
MyCollection.query({likes:{$gt:10}}, {limit:10, page:2});
When using the paging functionality, you will normally need to know the number of pages so that you can render
the correct interface for the user. Backbone Query can send the number of pages of results to a supplied callback.
The callback should be passed as a pager
property on the options object. This callback will also receive the sliced
models as a second variable.
Here is a coffeescript example of a simple paging setup using the pager callback option:
class MyView extends Backbone.View
initialize: ->
@template = -> #templating setup here
events:
"click .page": "change_page"
query_collection: (page = 1) ->
#Collection should be passed in when the view is instantiated
@collection.query {category:"javascript"}, {limit:5, page:page, pager:@render_pages}
change_page: (e) =>
page_number = $(e.target).data('page_number')
@query_collection page_number
render_pages: (total_pages, results) =>
content = @template results
pages = [1..total_pages]
nav = """
<nav>
<span>Total Pages: #{total_pages}</span>
"""
for page in pages
nav += "<a href='#' data-page_number='#{page}'>#{page}</a>"
nav += "</nav>"
@$el.html content + nav
render: => @query_collection()
Caching Results
To enable caching set the cache flag to true in the options object. This can greatly improve performance when paging
through results as the unpaged results will be saved. This options is not enabled by default as if models are changed,
added to, or removed from the collection, then the query cache will be out of date. If you know
that your data is static and won't change then caching can be enabled without any problems.
If your data is dynamic (as in most Backbone Apps) then a helper cache reset method is provided:
reset_query_cache
. This method should be bound to your collections change, add and remove events
(depending on how your data can be changed).
Cache will be saved in a _query_cache
property on each collection where a cache query is performed.
MyCollection.query({likes:{$gt:10}}, {limit:10, page:1, cache:true});
MyCollection.query({likes:{$gt:10}}, {limit:10, page:2, cache:true});
var MyCollection = Backbone.QueryCollection.extend({
initialize: function(){
this.bind("change", this.reset_query_cache, this);
}
});
Author
Dave Tonge - davidgtonge