Upload
philip-champon
View
9.112
Download
6
Tags:
Embed Size (px)
Citation preview
ActiveRecord & ARel
Awkward friends
ARel is not...
A replacement for ActiveRecord!
ARel is...
• Active Relation
• Really neat• Still under development
• The safest way to write SQL
• SQL engine agnostic!
• An OOP interpretation of Relational Algebra
• A DSL• Docs? Documentation?
Uh, he's sick.• Use it!• All examples assume
PSQL
How to use ARel
ARel can be used independently of ActiveRecord, but I'm not going to focus on that, because it's not my use case.
The easiest way to use ARel, with ActiveRecord, is to access the #arel_table of your ActiveRecord models. This method exposes the Arel::Table object associated with your model. Through this object you can do many ARel-y things.
There are a few other class level methods, which will be covered in a bit.
Accessing attributes
• Model.arel_table[:attr]
• Model.arel_table[:attr].as("alias").to_sql
The essential building block for AR & AR interaction. All of the model's attributes are available as keys to the arel_table.
"model"."attr" AS alias
Notice the result is canonically referenced and properly quoted (very portable)
Predicates
• Model.arel_table[:id].eq(1).to_sql• Model.arel_table[:id].not_eq(1).to_sql• Model.arel_table[:id].in([1,2]).to_sql• Model.arel_table[:id].not_in([1,2]).
to_sql• Model.arel_table[:name].matches("a%").
to_sql• Model.arel_table[:name].
does_not_match("a%").to_sql• Model.arel_table[:id].gt(1).to_sql• Model.arel_table[:id].gteq(1).to_sql• Model.arel_table[:id].lt(1).to_sql• Model.arel_table[:id].lteq(1).to_sql
• "model"."id" = 1• "model"."id" != 1• "model"."id" IN (1,2)• "model"."id" NOT IN
(1,2)• "model"."name" ILIKE
"a%"• "model"."name" NOT
ILIKE "a%"• "model"."id" > 1• "model"."id" >= 1• "model"."id" < 1• "model"."id" <= 1
Predicate alternates
Each basic predicate supports two additional types, _any and _all. These methods each accept arrays of values, ie
Model.arel_table[:id].eq_any([1,2]).to_sql=> SELECT "models".* FROM "models" WHERE ("models"."id" = 1 OR "models"."id" = 2)
Model.arel_table[:id].in_all([[1,2],[3,4]]).to_sql=> SELECT "models".* FROM "models" WHERE ("models"."id" IN (1,2) AND "models"."id" IN (3,4))
Aggregate Functions
• Model.arel_table[:int].average.to_sql
• Model.arel_table[:int].average.as("alias").to_sql
• Model.arel_table[:int].count.to_sql
• Model.arel_table[:int].count.as("alias").to_sql
• Model.arel_table[:int].maximum.to_sql
• Model.arel_table[:int].minimum.to_sql
• Model.arel_table[:int].sum.to_sql
• AVG("models"."int") AS avg_id
• AVG("models"."int") AS alias• COUNT("models"."int")• COUNT("models"."int") AS
alias• MAX("models"."int") AS
max_id• MIN("models"."int") AS
min_id• SUM("models"."int") AS
sum_id
Infix operators
m = Model.arel_table
• m[:int1] + m[:int2]• m[:int1] - m[:int2]• m[:int1] * m[:int2]• m[:int1] * m[:int2].as "int"• m[:int1] / m[:int2]• m[:int1] / m[:int2].as "int"• Model.select(
m[:int1] * m[:int2]. as("product")).to_sql
No support for bitwise operators or aliasing addition / subtraction
• (int1 + int2)• (int1 - int2)• int1 * int2• int1 * int2 AS int• int1 / int2• int1 / int2 AS int• SELECT
"models"."int1" * "models"."int2" AS product FROM "models"
Mixing ActiveRecord & ARel
Where inconsistencies collide
Selecting results
m = Model.arel_table
• Model.select([m[:id], m[:name]).to_sql
• Model.select(m[:int1].average.as("int1")).to_sql
• Model.select(m[:int1] + m[:int2]).to_sql
• SELECT "models"."id", "models"."name"FROM "models"
• SELECT AVG("models"."int1") AS int1FROM "models"
• SELECT ("models"."int1" + "models"."int2")FROM "models"
Filtering results (where)
m = Model.arel_table
• Model.where(m[:id].gteq(10)).to_sql
• Model.where(m.grouping(m[:id].gt(10). and(m[:name].matches("foo%")).or(m[:int1].in([1,2])))
• Model.where(m[:int1] + m[:int2].gteq(100))
• SELECT "models".*FROM "models"WHERE "models"."id" >= 10
• SELECT "models".*FROM "models"WHERE ((("models"."id" > 10 AND"models"."name" ILIKE "foo%")OR "models"."int1" IN (1,2)))
• SELECT "models".*FROM "models"WHERE "models"."int1" + "models"."int2" >= 100
Grouping and Having results
• ARelo Model.select(m[:name]).group(m[:name]).to_sql
• ActiveRecordo Model.select(:name).group(:name).to_sql
The ARel methods are important, because ActiveRecord does not canonically reference fields
• SELECT "models"."name" FROM "models" GROUP BY "models"."name"
• SELECT nameFROM "models" GROUP BY name
Ordering results
m = Model.arel_table
• m[:id].asc• m[:id].desc• m[:string].lower• ARel
o Model.order(m[:id].asc).to_sql
• ActiveRecordo Model.order(:id).to_sql
The ARel methods are important, because ActiveRecord does not canonically reference fields
• "models"."id" ASC• "models"."id" DESC• LOWER("models"."string")• SELECT "models".*
FROM "models" ORDER BY "models"."id" ASC
• SELECT "models".* FROM "models" ORDER BY id
Limiting results
• Model.skip(10).to_sql
• Model.take(100).to_sql
• Model.skip(10).take(100).to_sql
Take must be the last method in the chain, always.
• SELECT "models".*FROM "models"OFFSET 10
• SELECT "models".*FROM "models"LIMIT 100
• SELECT "models".*FROM "models"LIMIT 100OFFSET 10
Sub Selects
sub = Models.joins(:others). as("alias")
Models.select('*').from(sub).to_sql
SELECT * FROM ( SELECT "models".* FROM "models" INNER JOIN "others" ON "others"."model_id" = "models"."id") alias
Unions
m = Model.arel_tableo = Other.arel_table
• Model.select(m[:id]).union(Other.select(o[:id])).to_sql
• ( SELECT "models"."id" FROM "models" UNION SELECT "others"."id" FROM "others" )
Complex joins
Polymorphic join
m = Model.arel_tableo = Other.arel_tablesql = m.joins(o).on( m[:id].eq(o[:able_id]). and(o[:able_type].eq("O")) ).to_sqlModel.joins(sql).to_sql
SELECT "models".*FROM "models" INNER JOIN "others" ON ("models"."id" = "others"."able_id" AND "others"."able_type" = "O" )
Arbitrary SQL functions
m = Model.arel_table
• Model.select(Arel::Nodes::NamedFunction.new(:coalesce, [m[:id], 0])).to_sql
• Model.select(Arel::Nodes::NamedFunction.new(:coalesce, [m[:id], 0], "foo")).to_sql
• SELECT coalesce("models"."id", 0)FROM "models"
• SELECT coalesce("models"."id", 0) AS fooFROM "models"
Easier Arbitrary SQL Functions
config/initializers/arel_extensions.rb
Arel::Table.class_eval do |base| def function(name, expr, aliaz = nil) Arel::Nodes::NamedFunction.new(name, expr, aliaz) endend
m = Model.arel_tableModel.select(m.function(:coalesce, [m[:id], 0], "foo"))
Peeking and poking
m = Model.arel_tablequery = Model. joins(:others). merge(Other.where(id: 1)). order(m[:id].desc). skip(10). take(100)• query.join_sql• query.where_sql• query.to_sql
• INNER JOIN "others" ON "models"."id" = "others"."model_id"
• WHERE ("others"."id" = 1)• SELECT "models".*
FROM "model" INNER JOIN "others" ON "models"."id" = "others"."model_id"WHERE ("others"."id" = 1)ORDER BY "models"."id" DESC OFFSET 10 LIMIT 100
Links
• http://magicscalingsprinkles.wordpress.com/2010/01/28/why-i-wrote-arel/• https://github.com/rails/arel• http://www.railsdispatch.com/posts/activerelation• http://m.onkey.org/active-record-query-interface• http://www.bigbinary.com/videos/5-how-arel-works• https://github.com/stevecj/arel-presentation-slides/blob/master/one/01_slide.md• http://railscasts.com/episodes/215-advanced-queries-in-rails-3