This weekend I wanted to tackle some of rquery's shortcomings so I added the ability to |/& your operations and also the ability to use an object in the block instead of the symbol class for the column names. I agree with the assertion that it's bad to pollute other classes, but I still think the symbol.declaration syntax is pretty clean and there's isn't much likely hood of those methods ever being implemented elsewhere ( says the guy who decided to implement them :P )


The key bit is the ability to add multiple operations to the same line connected with | or & operators. In this way you can form extremely complex queries, where as before you were limited to whatever you could get out a series of anded logic. The only stipulation is that you you have to wrap the operations with parenthesis if they are on the same line, or ruby will complain about a syntax error.
#All operations need to be on the same line and in parens
#either the | operator or the & operator can be used on a singel line
User.where do |user|
  ( > 20) | ( 16,18)

The above example should be easy to follow but there is one important gotcha with including &'s and |'s on the same line. Because the & operator has precedence in a string of operations if you want both | and & on the same line you need to force precedence for the | where it needs to be evaluated before the & in the resulting query.
#the & takes precedence here 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|
  ( "George") | ( "Alice") & (user.age.from 20..30)

In this case the query writer wanted to get the users who's names contained either George or Alice and then filter with an age range. What it'll actually do is find the users who's name contains Alice and have an age between 20 and 30, and add anyone with a name like George. To fix this you can add more parens :(
#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|
 (( "George") | ( "Alice")) & (user.age.from 20..30)

Or you can simply move the and'd operation to the next line as the operations are evaluated top down as the ruby block is executed and they are grouped using and.
#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
#additionaly operations on seperate lines don't need parens
User.where do |user|
 ( "George") | ( "Alice")
 user.age.from 20..30

The biggest change here is simply being able to use the | instead of the default for each line (and). And finally, if you checked out RQuery before, you'll have noticed I'm passing in an object that represents the model being queried. The advantage is there's no class polution, and the not so obvious advantage is that it will throw and nice verbose and descriptive exception if you try and use an attribute that doesn't exist for a given model.

#should you attempt to use and attribute that doesn't exist for a given model
#rquery will tell you before it's sent to the db
User.where do |user| == "123-45-6789"
# RQuery::AttributeNotFoundError: The field 'ssn' doesn't exist for this object
#       from /Users/johnbender/Projects/rquery/lib/rquery/attrib...
#       from (irb):24
#       from /Users/johnbender/Projects/rquery/lib/rquery/active...
#       from /Users/johnbender/Projects/rquery/lib/rquery/active...
#       from /Users/johnbender/Projects/rquery/lib/rquery/active...
#       from (irb):23

My specs cover most of the code and functionality for the activerecord extension (still the only piece I've worked on) and you can still use the symbol syntax if you want.
#environment config

#example of using symbols, you can see more at the RQuery page on my site.
User.where do
 (:name.contains "George") | (:name.contains "Alice")
 :age.from 20..30

You can get the gem from github as johnbender-rquery, and I won't be updating the rubyforge one unless github fails to build my gem again ( /cross_fingers ).


26 Apr 2009