activerecord & arel

22
ActiveRecord & ARel Awkward friends

Upload: philip-champon

Post on 18-May-2015

9.112 views

Category:

Technology


6 download

TRANSCRIPT

Page 1: ActiveRecord & ARel

ActiveRecord & ARel

Awkward friends

Page 2: ActiveRecord & ARel

ARel is not...

A replacement for ActiveRecord!

Page 3: ActiveRecord & ARel

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

Page 4: ActiveRecord & ARel

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.

Page 5: ActiveRecord & ARel

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)

Page 6: ActiveRecord & ARel

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

Page 7: ActiveRecord & ARel

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))

Page 8: ActiveRecord & ARel

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

Page 9: ActiveRecord & ARel

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"

Page 10: ActiveRecord & ARel

Mixing ActiveRecord & ARel

Where inconsistencies collide

Page 11: ActiveRecord & ARel

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"

Page 12: ActiveRecord & ARel

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

Page 13: ActiveRecord & ARel

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

Page 14: ActiveRecord & ARel

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

Page 15: ActiveRecord & ARel

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

Page 16: ActiveRecord & ARel

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

Page 17: ActiveRecord & ARel

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" )

Page 18: ActiveRecord & ARel

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"     )

Page 19: ActiveRecord & ARel

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"

Page 20: ActiveRecord & ARel

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"))

Page 21: ActiveRecord & ARel

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

Page 22: ActiveRecord & ARel

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