Getting to know Arel
Active Record's nerdy little brother
What is Arel?
Arel is a SQL AST manager for Ruby
Active Record uses Arel to build queries
It adapts to various RDBMSes
It is intended to be a framework framework
Active Record is pretty good...
Dog.where(name: 'Fido')
# WHERE "dogs"."name" = 'Fido'
Dog.where(name: nil)
# WHERE "dogs"."name" IS NULL
Dog.where(name: ['Fido', 'Jeff'])
# WHERE "dogs"."name" IN ('Fido', 'Jeff')
Dog.where(name: ['Fido', 'Jeff', nil])
# WHERE (
# "dogs"."name" IN ('Fido', 'Jeff') OR
# "dogs"."name" IS NULL
# )
Active Record is pretty good,
until it isn't.
It only supports equality
Dog.where('age > ?', 5)
# WHERE age > 5
No support for OR (until 5.0.0)
Dog.where('age = ? OR name = ?', 5, 'Fido')
# WHERE age = 5 OR name = 'Fido'
No support for explicit joins
Dog.joins(
'INNER JOIN owners o ON o.id = dogs.owner_id'
)
No outer joins (without loading everything into memory)
Dog.joins(
'LEFT OUTER JOIN owners ON owners.id = dogs.owner_id'
)
Composability goes out the window
class Dog < ActiveRecord::Base
scope :old, -> { where('age > ?', 5) }
scope :named_fido, -> { where(name: 'Fido') }
def self.named_fido_or_old
where('age > ? OR name = ?', 5, 'Fido')
end
end
Not to mention...
Not database agnostic
No syntax checking
Question marks can be tough to track down
What ever happened to the 80 characters/line?
Model
.joins('LEFT JOIN candidacies as search_candidates on search_candidates.contact_id = contacts.id'
.joins('LEFT JOIN searches as contact_searches on search_candidates.search_id = contact_searches.id'
.where('(lower(contact_searches.name) like ? AND search_candidates.deleted=?)', "%#{name}%".downcase
Arel to the rescue!
Arel::Table
Arel::Table.new(:dogs)
Arel::Table.new(:dogs)[:name] # an Arel::Attribute
Dog.arel_table
Dog.arel_table[:name] # an Arel::Attribute
Predications
age = Dog.arel_table[:age]
name = Dog.arel_table[:name]
Dog.where age.gt(5)
# WHERE "dogs"."age" > 5
Dog.where age.not_eq(5)
# WHERE "dogs"."age" != 5
Dog.where name.matches('%ido')
# WHERE "dogs"."name" LIKE '%ido'
Grouping
id = Dog.arel_table[:id]
age = Dog.arel_table[:age]
name = Dog.arel_table[:name]
Dog.where id.gt(5).and(
name.eq('Ronald').or(
age.eq(3)
)
)
# WHERE (
# "dogs"."id" > 5 AND (
# "dogs"."name" = 'Ronald' OR
# "dogs"."age" = 3
# )
# )
Inner Join
dogs = Dog.arel_table
owners = Owner.arel_table
join = dogs.inner_join(owners).on(
dogs[:owner_id].eq(owners[:id])
)
Dog.joins join.join_sources
# SELECT "dogs".* FROM "dogs"
# INNER JOIN "owners"
# ON "dogs"."owner_id" = "owners"."id"
Outer Join
dogs = Dog.arel_table
owners = Owner.arel_table
join = dogs.outer_join(owners).on(
dogs[:owner_id].eq(owners[:id])
)
Dog.joins join.join_sources
# SELECT "dogs".* FROM "dogs"
# LEFT OUTER JOIN "owners"
# ON "dogs"."owner_id" = "owners"."id"
Composability
class Dog < ActiveRecord::Base
scope :old, -> { where(old_arel) }
scope :named_fido, -> { where(named_fido_arel) }
def self.old_or_named_fido
where named_fido_arel.or(old_arel)
end
def self.old_arel
arel_table[:age].gt(5)
end
def self.named_fido_arel
arel_table[:name].eq('Fido')
end
end
Arel can do anything!
Aggregates
Dog.select Dog.arel_table[:age].maximum
# SELECT MAX("dogs"."age") FROM "dogs"
Functions
Dog.select(
Arel::Nodes::NamedFunction.new('COALESCE', [
Dog.arel_table[:name],
Arel::Nodes.build_quoted('Ronald')
])
)
# SELECT COALESCE("dogs"."name", 'Ronald')
# FROM "dogs"
Infix
Dog.select(
Arel::Nodes::InfixOperation.new('||',
Dog.arel_table[:name],
Arel::Nodes.build_quoted('diddly')
)
)
# SELECT "dogs"."name" || 'diddly'
# FROM "dogs"
Dog.select("name || 'diddly'")
# SELECT.... nevermind...
Am I just wasting your time?
(this Arel stuff is pretty verbose)
Introducing Baby Squeel
Extremely similar to Squeel
Under 500 LOC
No core exts
No monkey patches!
As conservative as possible
Predications
Dog.where.has { age > 5 }
# WHERE ("dogs"."age" > 5)
Dog.where.has { name != 'Jeff' }
# WHERE ("dogs"."name" != 'Jeff')
Dog.where.has { name =~ '%ido' }
# WHERE ("name" LIKE '%ido')
Grouping
Dog.where.has {
(id > 5).and(
(name == 'Ronald').or(age == 3)
)
}
# WHERE (
# "dogs"."id" > 5 AND (
# "dogs"."name" = 'Ronald' OR
# "dogs"."age" = 3
# )
# )
Dog.selecting { age.maximum }
# SELECT MAX("dogs"."age") FROM "dogs"
Dog.selecting { (id + 100) / 20 }
# SELECT ("dogs"."id" + 100) / 20 FROM "dogs"
Dog.selecting { coalesce(name, quoted('Ronald')) }
# SELECT coalesce("dogs"."name", 'Ronald') FROM "dogs"
Dog.selecting { name.op('||', quoted('diddly')) }
# SELECT "dogs"."name" || 'diddly' FROM "dogs"
Explicit Joins
Dog.joining { owner.on(owner_id == owner.id) }
# INNER JOIN "owners"
# ON "dogs"."owner_id" = "owners"."id"
Dog.joining { owner.outer.on(owner.id == owner_id) }
# LEFT OUTER JOIN "owners"
# ON "dogs"."owner_id" = "owners"."id"
Implicit Joins
class Dog < ActiveRecord::Base
belongs_to :owner
end
Dog.joining { owner }
# INNER JOIN "owners"
# ON "owners"."id" = "dogs"."owner_id"
Dog.joining { owner.outer }
# LEFT OUTER JOIN "owners"
# ON "owners"."id" = "dogs"."owner_id"
Join like a BO$$
Dog.joining { owner.dog.outer.owner.dog }
# SELECT "dogs".* FROM "dogs"
# INNER JOIN "owners"
# ON "owners"."id" = "dogs"."owner_id"
# LEFT OUTER JOIN "dogs" "dogs_owners"
# ON "dogs_owners"."owner_id" = "owners"."id"
# INNER JOIN "owners" "owners_dogs"
# ON "owners_dogs"."id" = "dogs_owners"."owner_id"
# INNER JOIN "dogs" "dogs_owners_2"
# ON "dogs_owners_2"."owner_id" = "owners_dogs"."id"
Composability
class Dog < ActiveRecord::Base
sifter(:old) { age > 5 }
sifter(:named_fido) { name == 'Fido' }
scope :old, -> { where.has { sift(:old) } }
scope :named_fido, -> { where.has { sift(:named_fido) } }
def self.old_or_named_fido
where.has { sift(:old) | sift(:named_fido) }
end
end
Thanks.
Now, please stop using strings to generate SQL.

Getting to know Arel

  • 1.
    Getting to knowArel Active Record's nerdy little brother
  • 2.
    What is Arel? Arelis a SQL AST manager for Ruby Active Record uses Arel to build queries It adapts to various RDBMSes It is intended to be a framework framework
  • 3.
    Active Record ispretty good...
  • 4.
    Dog.where(name: 'Fido') # WHERE"dogs"."name" = 'Fido'
  • 5.
    Dog.where(name: nil) # WHERE"dogs"."name" IS NULL
  • 6.
    Dog.where(name: ['Fido', 'Jeff']) #WHERE "dogs"."name" IN ('Fido', 'Jeff')
  • 7.
    Dog.where(name: ['Fido', 'Jeff',nil]) # WHERE ( # "dogs"."name" IN ('Fido', 'Jeff') OR # "dogs"."name" IS NULL # )
  • 8.
    Active Record ispretty good, until it isn't.
  • 9.
    It only supportsequality Dog.where('age > ?', 5) # WHERE age > 5
  • 10.
    No support forOR (until 5.0.0) Dog.where('age = ? OR name = ?', 5, 'Fido') # WHERE age = 5 OR name = 'Fido'
  • 11.
    No support forexplicit joins Dog.joins( 'INNER JOIN owners o ON o.id = dogs.owner_id' )
  • 12.
    No outer joins(without loading everything into memory) Dog.joins( 'LEFT OUTER JOIN owners ON owners.id = dogs.owner_id' )
  • 13.
    Composability goes outthe window class Dog < ActiveRecord::Base scope :old, -> { where('age > ?', 5) } scope :named_fido, -> { where(name: 'Fido') } def self.named_fido_or_old where('age > ? OR name = ?', 5, 'Fido') end end
  • 14.
    Not to mention... Notdatabase agnostic No syntax checking Question marks can be tough to track down What ever happened to the 80 characters/line? Model .joins('LEFT JOIN candidacies as search_candidates on search_candidates.contact_id = contacts.id' .joins('LEFT JOIN searches as contact_searches on search_candidates.search_id = contact_searches.id' .where('(lower(contact_searches.name) like ? AND search_candidates.deleted=?)', "%#{name}%".downcase
  • 15.
    Arel to therescue!
  • 16.
    Arel::Table Arel::Table.new(:dogs) Arel::Table.new(:dogs)[:name] # anArel::Attribute Dog.arel_table Dog.arel_table[:name] # an Arel::Attribute
  • 17.
    Predications age = Dog.arel_table[:age] name= Dog.arel_table[:name] Dog.where age.gt(5) # WHERE "dogs"."age" > 5 Dog.where age.not_eq(5) # WHERE "dogs"."age" != 5 Dog.where name.matches('%ido') # WHERE "dogs"."name" LIKE '%ido'
  • 18.
    Grouping id = Dog.arel_table[:id] age= Dog.arel_table[:age] name = Dog.arel_table[:name] Dog.where id.gt(5).and( name.eq('Ronald').or( age.eq(3) ) ) # WHERE ( # "dogs"."id" > 5 AND ( # "dogs"."name" = 'Ronald' OR # "dogs"."age" = 3 # ) # )
  • 19.
    Inner Join dogs =Dog.arel_table owners = Owner.arel_table join = dogs.inner_join(owners).on( dogs[:owner_id].eq(owners[:id]) ) Dog.joins join.join_sources # SELECT "dogs".* FROM "dogs" # INNER JOIN "owners" # ON "dogs"."owner_id" = "owners"."id"
  • 20.
    Outer Join dogs =Dog.arel_table owners = Owner.arel_table join = dogs.outer_join(owners).on( dogs[:owner_id].eq(owners[:id]) ) Dog.joins join.join_sources # SELECT "dogs".* FROM "dogs" # LEFT OUTER JOIN "owners" # ON "dogs"."owner_id" = "owners"."id"
  • 21.
    Composability class Dog <ActiveRecord::Base scope :old, -> { where(old_arel) } scope :named_fido, -> { where(named_fido_arel) } def self.old_or_named_fido where named_fido_arel.or(old_arel) end def self.old_arel arel_table[:age].gt(5) end def self.named_fido_arel arel_table[:name].eq('Fido') end end
  • 22.
    Arel can doanything!
  • 23.
  • 24.
  • 25.
  • 26.
    Am I justwasting your time? (this Arel stuff is pretty verbose)
  • 27.
  • 28.
    Extremely similar toSqueel Under 500 LOC No core exts No monkey patches! As conservative as possible
  • 29.
    Predications Dog.where.has { age> 5 } # WHERE ("dogs"."age" > 5) Dog.where.has { name != 'Jeff' } # WHERE ("dogs"."name" != 'Jeff') Dog.where.has { name =~ '%ido' } # WHERE ("name" LIKE '%ido')
  • 30.
    Grouping Dog.where.has { (id >5).and( (name == 'Ronald').or(age == 3) ) } # WHERE ( # "dogs"."id" > 5 AND ( # "dogs"."name" = 'Ronald' OR # "dogs"."age" = 3 # ) # )
  • 31.
    Dog.selecting { age.maximum} # SELECT MAX("dogs"."age") FROM "dogs" Dog.selecting { (id + 100) / 20 } # SELECT ("dogs"."id" + 100) / 20 FROM "dogs" Dog.selecting { coalesce(name, quoted('Ronald')) } # SELECT coalesce("dogs"."name", 'Ronald') FROM "dogs" Dog.selecting { name.op('||', quoted('diddly')) } # SELECT "dogs"."name" || 'diddly' FROM "dogs"
  • 32.
    Explicit Joins Dog.joining {owner.on(owner_id == owner.id) } # INNER JOIN "owners" # ON "dogs"."owner_id" = "owners"."id" Dog.joining { owner.outer.on(owner.id == owner_id) } # LEFT OUTER JOIN "owners" # ON "dogs"."owner_id" = "owners"."id"
  • 33.
    Implicit Joins class Dog< ActiveRecord::Base belongs_to :owner end Dog.joining { owner } # INNER JOIN "owners" # ON "owners"."id" = "dogs"."owner_id" Dog.joining { owner.outer } # LEFT OUTER JOIN "owners" # ON "owners"."id" = "dogs"."owner_id"
  • 34.
    Join like aBO$$ Dog.joining { owner.dog.outer.owner.dog } # SELECT "dogs".* FROM "dogs" # INNER JOIN "owners" # ON "owners"."id" = "dogs"."owner_id" # LEFT OUTER JOIN "dogs" "dogs_owners" # ON "dogs_owners"."owner_id" = "owners"."id" # INNER JOIN "owners" "owners_dogs" # ON "owners_dogs"."id" = "dogs_owners"."owner_id" # INNER JOIN "dogs" "dogs_owners_2" # ON "dogs_owners_2"."owner_id" = "owners_dogs"."id"
  • 35.
    Composability class Dog <ActiveRecord::Base sifter(:old) { age > 5 } sifter(:named_fido) { name == 'Fido' } scope :old, -> { where.has { sift(:old) } } scope :named_fido, -> { where.has { sift(:named_fido) } } def self.old_or_named_fido where.has { sift(:old) | sift(:named_fido) } end end
  • 36.
    Thanks. Now, please stopusing strings to generate SQL.