activerecord & arel

Post on 18-May-2015

9.112 Views

Category:

Technology

6 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

top related