Post on 23-Feb-2017
transcript
Getting to know ArelActive 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') endend
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_tableDog.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_tableowners = 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_tableowners = 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') endend
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 :ownerend
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) } endend
Thanks.Now, please stop using strings to generate SQL.