Recently I was browsing Django documentation deliberately looking for some ideas to cross-pollinate to Rails. F() and Q() objects looked like nice little hacks so I decided to implement them for Rails.
Q() Object
Even though it’s not nearly as powerful as his Star Trek namesake
it’s still pretty interesting to look at (original docs available
here).
It tackles the problem of complex SQL conditions building.
Usually most issues arise from the fact that simple hash based conditions
don’t allow for negated or ORed conditions. There are plenty of
other projects aimed to mitigate these restrictions among others (a while
ago I assembled the collection of these):
Squirrel,
Ambition,
dm-sugar-glider,
ez_where,
RQuery,
Searchlogic,
Sequel,
probably most recent one is
MetaWhere.
There are different tricks above libraries employ
-
instance_evaland/ormethod_missingletting you write# Squirrel Playlist.find(:all) do any do name == "Party Mix" total_length > 3600 end endor# RQuery User.where do |user| (user.age > 20) | (user.age.in 16,18) end - core objects extensions
(extending
Hashand/orSymbolobjects)# Sequel Artist.filter(:name.like('Y%') & ({:b=>1} | ~{:c=>3})) # SELECT * FROM artists WHERE name LIKE 'Y%' AND (b = 1 OR c != 3) -
hooking the interpreter (accessing syntax tree of the condition
specifying block)
# Ambition >> SQL::User.select { |m| m.name == 'jon' && m.age == 21 }.to_s => "SELECT * FROM users WHERE users.name = 'jon' AND users.age = 21"
While there is nothing technically wrong with the approaches listed above, it’s interesting to see what can be done without resorting to any of these tricks. The Q() object fits there very well. It let’s you build ORed, ANDed, and negated conditions without dropping to writing SQL (drawing on simple and well known hash-based syntax). Below is the illustration of Q() based syntax and the corresponding SQL fragments generated by it
~Q(:user_id => nil) # => user_id IS NOT NULL
Q(:user_id => nil) | Q(:id => nil) # => user_id IS NULL OR id IS NULL
~(Q(:user_id => nil) & Q(:id => nil)) # => user_id IS NOT NULL AND id IS NOT NULL
Ruby 1.9 allows ! operator overloading leading to more
natural syntax for negated conditions
!Q(:user_id => nil) # => user_id IS NOT NULL
F() Object
Original docs. It let’s you reference your database columns and do simple calculations on them from your conditions and update statements. The concept is pretty simple and can be illustrated with a few examples
# Simple column reference
User.where(:updated_at => F(:created_at))
# instead of writing it in SQL like: User.where("updated_at = created_at")
# Column reference with calculation
User.where(:updated_at => F(:created_at) + 1)
# instead of User.where("updated_at = created_at + 1")
# Calculation with references also work
User.where(:updated_at => F(:created_at) + F(:updated_at))
# instead of User.where("updated_at = created_at + updated_at")
# Use in update statement
User.update_all(:id_copy => F(:id))
# instead of User.update_all("id_copy = id")
Nice side effect of using this is that you get atomic operations for free
user = User.find(1)
user.views = F(:views) + 1 #
user.save
Whenever the save is called it’s the current `views`
that will be incremented (not the value that that was loaded with
User.find).
Installation
You can get the code from my eponymous gem like this
> gem install dolzenko
> irb
ruby-head > require 'dolzenko/django_q_object'
ruby-head > require 'dolzenko/django_f_object'