This document discusses how Arel, the SQL abstraction library underlying ActiveRecord, brings relational algebra and operators to ActiveRecord queries. It provides examples of how relations can be combined and manipulated to perform more complex queries. Key points covered include the SELECT, WHERE, JOIN, and other SQL operators Arel supports and how ActiveRecord::Relations allow building queries incrementally prior to execution.
2. What!?
• Relational Algebra deals with sets of finite relations, which are closed under
certain operators. These operators operate on one or more relations to yield a
relation.
• Relation (table) - Data structure composed of a heading (columns) and an
unordered set of tuples (rows) that share the same type.
• Closure - A set is said to be closed under some operation if the result of that
operation is a member of the set.
• e.g.: Real numbers are closed under subtraction but natural numbers are not.
Although 3 and 7 are natural numbers the result of 3 - 7 is not.
3. Operators
Primitive Derived
Selection (WHERE) Set Intersection
Projection (SELECT) Division
Cross Join Natural Join
Set Union
Set Difference
Rename (SELECT A as B)
4. ActiveRecord::Relation Example
Pub.select(:name).where(:city => 'Sydney')
#yields an ActiveRecord::Relation instance. No SQL ran.
but prior to Rails 3...
Pub.find(:all, :select => "name",
:conditions => { :city => 'Sydney' })
#yields an Array with the results
5. Ok, but now what?
Build more interesting queries by combining relations...
> syd_pubs = Pub.select('pubs.name').where(:city => 'Sydney')
> squire = Pub.joins(:beers).where(:beers =>
{:name => 'James Squire'})
Now get me all the pubs in sydney that sell James Squire!
> (syd_pubs & squire).all
# yields an Array
6. Arel in Action
> (syd_pubs & squire).all
This causes ActiveRecord::Relation to ask Arel to build the SQL
query below, corresponding to the combined relations:
SELECT pubs.name
FROM pubs
INNER JOIN beers_pubs
ON beers_pubs.pub_id = pubs.id
INNER JOIN beers
ON beers.id = beers_pubs.beer_id
WHERE (pubs.city = 'Sydney') AND
(beers.name = 'James Squire')
7. Grouping and sorting data
> pubs_beers = Pub.joins(:beers)
> pubs = pubs_beers.select('pubs.name, count(*) as
beers_count').group('pubs.name')
> pubs.all[0].beers_count
# 2
> pubs.order('beers_count').all
8. Range Conditions
> today = Time.now.at_beginning_of_day
> tomorrow = Time.now.tomorrow.at_beginning_of_day
> Pub.where(:created_at => today..tomorrow).all
Arel takes care of ranges as well...
SELECT pubs.*
FROM pubs
WHERE (pubs.created_at BETWEEN
'2010-10-05 00:00:00.000000'
AND '2010-10-06 00:00:00.000000')
9. Scopes: Reusable Relations
class Beer < ActiveRecord::Base
scope :stout, where(:flavor => "Stout")
end
> Beer.stout.all
# gives back an Array with the results
10. Eager Loading Associations
Associations are lazily loaded by default and as such...
> pubs = Pub.all
> pubs[0].beers
> pubs[1].beers
...produces these queries to be executed:
SELECT pubs.* FROM pubs
SELECT * FROM beers
INNER JOIN beers_pubs
ON beers.id = beers_pubs.beer_id
WHERE (beers_pubs.pub_id = 1 )
SELECT * FROM beers
INNER JOIN beers_pubs
ON beers.id = beers_pubs.beer_id
WHERE (beers_pubs.pub_id = 2 )
11. Eager Loading Associations
Eager load what you need using the new relations syntax:
> pubs = Pub.includes(:beers).all
> pubs[0].beers
> pubs[1].beers
Note that only one query is used to load the Beers info:
SELECT pubs.* FROM pubs
SELECT beers.*, t0.pub_id as the_parent_record_id
FROM beers
INNER JOIN beers_pubs t0
ON beers.id = t0.beer_id
WHERE (t0.pub_id IN (1,2,3))