Querying
NoBrainer supports a rich query language that supports a wide range of features. This section is organized in two parts. The first one describes methods used to construct criteria, and the second one describes the methods that evaluate criteria.
Constructing Criteria
all
Model.all
yields a criterion that can be chained with other criteria.
This is not so useful as most of the chainable criteria and terminators can be
directly called on the Model class. For example Model.each { }
and Model.all.each { }
are equivalent.
where()
The where()
method selects documents for which its given predicates are true.
The rules are the following:
-
where(p)
returns the documents that matches the predicates p. -
where(p1).where(p2)
is equivalent towhere(p1, p2)
The predicates are described below:
[p1,...,pN]
evaluates to:and => [p1,...,pN]
.:and => [p1,...,pN]
: evaluates to true when all the predicates are true.:or => [p1,...,pN]
: evaluates to true when at least one of the predicates is true.
Be aware that[:a => 1, :b => 2]
is the same as[{:a => 1, :b => 2}]
, which is not the same as[{:a => 1}, {:b => 2}]
. NoBrainer prevents the former usage to avoid programming mistakes. If you know what you are doing, you may use:_or
.:not => p
: evaluates to true whenp
is false.:attr => value
evaluates to:attr.eq => value
:attr.eq => /regexp/
evaluates to true whenattr
matches the regular expression.:attr.eq => (min..max)
evaluates to true whenattr
is between the range.:attr.eq => value
evaluates to true whenattr
is equal tovalue
.:attr.gt => value
evaluates to true whenattr
is greater thanvalue
.:attr.ge => value
evaluates to true whenattr
is greater than or equal tovalue
.:attr.gte => value
evaluates to:attr.ge => value
:attr.lt => value
evaluates to true whenattr
is less thanvalue
.:attr.le => value
evaluates to true whenattr
is less than or equal tovalue
.:attr.lte => value
evaluates to:attr.le => value
.:attr.defined => true
evaluates to true whenattr
is defined.:attr.undefined => true
evaluates to true whenattr
is undefined.:attr.in => [value1,...,valueN]
evaluates to true whenattr
is in the specified array.:attr.include => value
evaluates to true when the arrayattr
includesvalue
.:attr.during => (start_time, end_time)
evaluates to true whenattr
is between the start and end time range.:attr.any.op => value
evalues to true when any of theattr
values matchvalue
withop
.:attr.all.op => value
evalues to true when all of theattr
values matchvalue
withop
.:attr.not.op => value
evalues to true when theattr
value does not matchvalue
withop
.lambda { |doc| rql_expression(doc) }
evaluates the RQL expression.
A couple of notes:
-
:attr.keyword => value
can also be written as:attr.keyword value
. -
where()
will try to use one of your declared indexes for performance. Learn more about indexes in the Indexes section. For example, when using thein
keyword, NoBrainer will use theget_all()
command if there is an index declared onattr
. Otherwise, NoBrainer will construct a query equivalent toModel.where(:or => [:attr => value1,...,:attr => valueN])
. When using comparison operators, NoBrainer leverage the RQLbetween()
command if an index is available onattr
. When using theany
keyword, NoBrainer tries to use amulti
index if available. -
Model.where(:attr => value1).where(:attr => value2)
will match no documents ifvalue1 != value2
, even when using adefault_scope
. -
where()
can also take a block to specify an additional RQL filter. -
where()
also accept belongs_to associations. In which case, the foreign key is used. For exampleComment.where(:post => Post.first)
is valid.Post.first.comments
is better though. -
Nested hash queries are supported. For example
where(:address => {:state.in => %w(NY CA)})
matches thestate
attribute from theaddress
hash. -
where()
performs type checking and only operates on defined attributes. To bypass these checks, you may use_where()
.
As an example, one can construct such query:
order_by()/reverse_order/without_ordering
order_by()
allows to specify the ordering in which the documents are returned.
Below a couple of examples to show the usage of order_by()
:
order_by(:field1 => :asc, :field2 => :desc)
orders by field1 ascending first, and then field2 descending. This syntax works because since Ruby 1.9, hashes are ordered.order_by(:field1, :field2)
is equivalent toorder_by(:field1 => :asc, :field2 => :asc)
order_by { |doc| doc[:field1] + doc[:field2] }
sorts by the sum of field1 and field2 ascending.order_by(->(doc) { doc[:field1] + doc[:field2] } => :desc)
sorts by the sum of field1 and field2 descending.criteria.reverse_order
yields criteria with the opposite ordering.criteria.without_ordering
yields criteria with no ordering.- The latest specified
order_by()
wins. For example,order_by(:field1).order_by(:field2)
is equivalent toorder_by(:field2)
.
NoBrainer always order by ascending primary keys by default.
order_by()
will try to use one of your declared indexes for performance when
possible. Learn more about indexes in the Indexes section.
skip()/offset()/limit()
criteria.skip(n)
will skipn
documents.criteria.offset(n)
is an alias forcriteria.skip(n)
.criteria.limit(n)
limits the number of returned documents ton
.
When compiling the RQL query, the skip/limit directives are applied at the end of the RQL query, regardless of their position on the criteria.
raw
criteria.raw
will no longer output model instances, but attribute hashes as received from the database.
unscoped
criteria.unscoped
will disable the default scope.
eager_load()
criteria.eager_load(:some_association)
eager loads the association. Read more about eager loading in the Eager Loading section.
count
criteria.count
returns the number of documents that matches the criteria.criteria.empty?
is an alias forcount == 0
criteria.any?
is an alias forcount != 0
if no block is given. When a block is given, the method call is proxied toto_a
.
update_all/replace_all/delete_all/destroy_all
criteria.update_all
update all documents matching the criteria following ther.update()
semantics.criteria.replace_all
replaces all documents matching the criteria following ther.replace()
semantics.criteria.delete_all
deletes all documents matching the criteria following ther.delete()
semantics.criteria.destroy_all
instantiates the models, run the destroy callbacks and deletes the documents. Returns the array of destroyed instances.
each/to_a
criteria.each
enumerates over the documents.criteria.to_a
returns an array of documents matching the criteria.criteria.some_method_of_array
will proxysome_method_of_array
toto_a
first, first!, last, last!, sample
criteria.first
returns the first matched document.criteria.last
returns the last matched document.criteria.first!
returns the first matched document, raises if not found.criteria.last!
returns the last matched document, raises if not found.criteria.sample
returns a document picked at random from a uniform distribution.criteria.sample(n)
returns an array ofn
documents picked at random from a uniform distribution.
The bang flavors raise a NoBrainer::Error::DocumentNotFound
exception if not found
instead of returning nil
. If left uncaught in a Rails controller, a 404
status code is returned.
find()
Model.find(id)
is equivalent toModel.where(:id => id).first!
.Model.find?(id)
is equivalent toModel.where(:id => id).first
.
Note that default scopes still apply.
first_or_create, first_or_create!
See the Persistence section.
changes
criteria.raw.changes(options={})
returns an changefeed similar tor.changes()
.
Note that you will get raw objects, and not model instances. If you’d like to get regular objects, please make a request on GitHub.
min, max, avg, sum
criteria.min(:field)
returns a document which has the minimum value offield
.criteria.max(:field)
returns a document which has the maximum value offield
.criteria.avg(:field)
returns the average offield
values.criteria.sum(:field)
returns the sum offield
values.
Note that you may also pass a lambda expression instead of a field. For example,
criteria.min { |doc| doc['field1'] + doc['field2'] }
returns a document
for which field1 + field2
is minimum
Geo queries
-
where(:field.near => circle)
returns documents whichfield
is withincircle
. Thecircle
must be coercible to aNoBrainer::Geo::Circle
(c.f. types). You may pass an additional option:max_results
when leveraging indexes withr.get_nearest
. -
where(:field.intersects => shape)
returns documents whichfield
intersectsshape
. Theshape
can be aNoBrainer::Geo::LineString
,NoBrainer::Geo::Circle
, orNoBrainer::Geo::Polygon
.
after_find()
criteria.after_find(->(doc) { puts "Loaded #{doc}" })
runs the specified callback whenever a document is instantiated through the criteria.
Multiple callbacks can be passed by calling after_find
multiple times.
after_find
accepts lambdas as arguments or blocks.
Calling reload
on an instance will not trigger these callbacks again.
Note that Model.after_find().first
declares a callback on the
model class, which triggers the callback on every fetched instance.
Using Model.all.after_find().first
only triggers the callback for that
specific fetched instance.
The Callbacks section describes the order in which the
after_find
callback is executed.
The after_find
feature is used internally by the has_many
association to set the
corresponding reverse belongs_to
association.
extend()
criteria.extend(module, ...)
extends the current criteria and any chained criteria with the given modules. Note that a block may also be given toextend()
.
Manipulating Criteria
You can merge two criteria with merge()
or merge!()
:
To retrieve the selecting criteria of a model instance, you may use instance.selector
.
This selector is used internally and is equivalent to r.table('models').get(instance.id)
.