RQuery is a small DSL inspired by RSpec for building text queries in languages like SQL. It is meant to be concise, easy to read, and expressive about what the query will be asking for.
Currently only the ActiveRecord extension is implemented with a Sqlite adapter. Mysql should be trivial to implement, and I'm hoping to get to supporting my own project Grove.
###Setup/Config
In you're rails environment file simply require rquery. The default adapter is the included Sqlite adapter but you can created and set your own with
RQuery::Config.adapter = class
You can view both Sql and Sqlite in the adapters directory if you are interested in writing your own (mysql?). As a side note it would be nice at some point to decide the adapter based on the db chosen for a given environment.
###Sniper Scopes
Its now extremely easy to use rquery's dsl as named scopes. For example
class User < ActiveRecord::Base
sniper_scope :with_name do |user, passed_name|
user.name == passed_name
end
end
User.with_name('George')
The first parameter will be an object representing the user and the rest will be the parameters passed when the method is called. Since this functionality makes use of the named_scope method it can be chained with other sniper_scopes and named_scopes. If the name seems a bit odd, I'm open to other suggestions :D
###Examples
RQuery extend ActiveRecord to provide the where
method. where
accepts a single optional argument and a block that represents the query statements
In a given UsersController your show
action might find the User like so:
@user = User.find(params[:age])
Using RQuery:
@user = User.where { |user| user.age == params[:age] }
In the above case, RQuery doesn't provide much of an improvement over the traditional find
method, but as the query becomes more complex its expressiveness begins to shine through:
@users = User.find(:all, conditions => ["age between ? and ?", 10 , 20])
RQuery:
@users = User.where do |user|
user.age.between 10..20
end
Both the from
and between
methods accept argument lists 10,20
or an array [10,20]
.
###Other Examples
RQuery supports most of the common SQL operations: =, <>, >, <, >=, <= as well as in, like (see below for specifics), and between. obj.foo.not_<operation>
works for .in
and .between
with the negation of == being obj.foo.not =
and the negation of obj.foo.contains
as obj.foo.without
.
Operators:
@obj = ActiveRecordObject.where do |obj|
obj.foo > 2
obj.foo.not = 4
end
#=> conditions array: ["foo > ? and foo <> ?", 2, 4]
Contains:
@obj = ActiveRecordObject.where do |obj|
obj.foo.contains "bar"
end
#=> conditions array: ["foo like '%' || ? || '%'", "bar"]
#using the default sqlite adapter
In:
@obj = ActiveRecordObject.where do |obj|
obj.foo.in "bar", "baz", "bak"
end
#=> conditions array: ["foo in (?)", ["bar", "baz", "bak"]]
#using the default sqlite adapter
You can also limit the results returned in a similar manner to the find
method by passing a symbol argument to the where method. The default is :all
, when no option is specified.
First:
@obj = ActiveRecordObject.where(:first) do |obj|
obj.foo == "bar"
end
is equivalent to the find call:
@obj = ActiveRecordObject.find(:first, conditions => ["foo = ?", "bar"])
###Complex queries
RQuery supports relatively complex queries including | and & operation groupings. All operations need to be on the same line and in parens and either the | operator or the & operator can be used on a singel line
User.where do |user|
(user.age > 20) | (user.age.in 16,18)
end
In the following example the & takes precedence and will be grouped with the contains "Alice" which will be or'd with the contains "George"
#=> name contains "George" or (name contains "Alice and age from 20 to 30)
User.where do |user|
(user.name.contains "George") | (user.name.contains "Alice") & (use.age.from 20..30)
end
To correct the above to the more intuitive version add parens to force precedence of the contains operations
#=> (name contains "George" or name contains "Alice) and age from 20 to 30
User.where do |user|
((user.name.contains "George") | (user.name.contains "Alice")) & (use.age.from 20..30)
end
In this sutation it would be cleaner and easier to just move the and'd statement down a line as all seperate lines are and'd and lines have precedence from top to bottom
User.where do |user|
(user.name.contains "George") | (user.name.contains "Alice")
use.age.from 20..30
end