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 whenpis false.:attr => valueevaluates to:attr.eq => value:attr.eq => /regexp/evaluates to true whenattrmatches the regular expression.:attr.eq => (min..max)evaluates to true whenattris between the range.:attr.eq => valueevaluates to true whenattris equal tovalue.:attr.gt => valueevaluates to true whenattris greater thanvalue.:attr.ge => valueevaluates to true whenattris greater than or equal tovalue.:attr.gte => valueevaluates to:attr.ge => value:attr.lt => valueevaluates to true whenattris less thanvalue.:attr.le => valueevaluates to true whenattris less than or equal tovalue.:attr.lte => valueevaluates to:attr.le => value.:attr.defined => trueevaluates to true whenattris defined.:attr.undefined => trueevaluates to true whenattris undefined.:attr.in => [value1,...,valueN]evaluates to true whenattris in the specified array.:attr.include => valueevaluates to true when the arrayattrincludesvalue.:attr.during => (start_time, end_time)evaluates to true whenattris between the start and end time range.:attr.any.op => valueevalues to true when any of theattrvalues matchvaluewithop.:attr.all.op => valueevalues to true when all of theattrvalues matchvaluewithop.:attr.not.op => valueevalues to true when theattrvalue does not matchvaluewithop.lambda { |doc| rql_expression(doc) }evaluates the RQL expression.
A couple of notes:
-
:attr.keyword => valuecan 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 theinkeyword, 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 theanykeyword, NoBrainer tries to use amultiindex 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.commentsis better though. -
Nested hash queries are supported. For example
where(:address => {:state.in => %w(NY CA)})matches thestateattribute from theaddresshash. -
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:
Model.where(:or => [->(doc) { doc[:field1] < doc[:field2] },
:field3.in ['hello', 'world'])
.where(:field4 => /ohai/, :field5.any.gt(4))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_orderyields criteria with the opposite ordering.criteria.without_orderingyields 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 skipndocuments.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.rawwill no longer output model instances, but attribute hashes as received from the database.
unscoped
criteria.unscopedwill 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.countreturns the number of documents that matches the criteria.criteria.empty?is an alias forcount == 0criteria.any?is an alias forcount != 0if 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_allupdate all documents matching the criteria following ther.update()semantics.criteria.replace_allreplaces all documents matching the criteria following ther.replace()semantics.criteria.delete_alldeletes all documents matching the criteria following ther.delete()semantics.criteria.destroy_allinstantiates the models, run the destroy callbacks and deletes the documents. Returns the array of destroyed instances.
each/to_a
criteria.eachenumerates over the documents.criteria.to_areturns an array of documents matching the criteria.criteria.some_method_of_arraywill proxysome_method_of_arraytoto_a
first, first!, last, last!, sample
criteria.firstreturns the first matched document.criteria.lastreturns 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.samplereturns a document picked at random from a uniform distribution.criteria.sample(n)returns an array ofndocuments 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 offieldvalues.criteria.sum(:field)returns the sum offieldvalues.
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 whichfieldis withincircle. Thecirclemust be coercible to aNoBrainer::Geo::Circle(c.f. types). You may pass an additional option:max_resultswhen leveraging indexes withr.get_nearest. -
where(:field.intersects => shape)returns documents whichfieldintersectsshape. Theshapecan 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!():
criteria3 = criteria1.merge(criteria2)
criteria1.merge!(criteria2)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).