advanced arel: when activerecord just isn't enough

160
ADVANCED AREL WHEN ACTIVERECORD JUST ISN’T ENOUGH Author.where( name: "Cameron Dutro", username: "@camertron" )

Upload: cameron-dutro

Post on 27-Aug-2014

1.725 views

Category:

Software


4 download

DESCRIPTION

Talk given at RailsConf 2014 about using Arel to build database queries of arbitrary complexity.

TRANSCRIPT

Page 1: Advanced Arel: When ActiveRecord Just Isn't Enough

ADVANCED ARELWHEN ACTIVERECORD JUST ISN’T ENOUGH

Author.where( name: "Cameron Dutro", username: "@camertron")

Page 2: Advanced Arel: When ActiveRecord Just Isn't Enough

`WHOAMI`

CAMERON DUTROINTERNATIONAL MAN OF MYSTERYUS GOVERNMENT

@CAMERTRONGITHUB.COM/CAMERTRON

Page 3: Advanced Arel: When ActiveRecord Just Isn't Enough

`WHOAMI`

CAMERON DUTROINTERNATIONAL MAN OF MYSTERYUS GOVERNMENT

@CAMERTRONGITHUB.COM/CAMERTRON

Page 4: Advanced Arel: When ActiveRecord Just Isn't Enough

`WHOAMI`

CAMERON DUTROINTERNATIONAL ENGINEERINGTWITTER, INC

@CAMERTRONGITHUB.COM/CAMERTRON

Page 5: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 6: Advanced Arel: When ActiveRecord Just Isn't Enough

RAISE YOUR HAND IF...

Posts.find(:all, joins: [ "JOIN comments ON comments.post_id = posts.id", "JOIN authors ON authors.id = comments.author_id" ], conditions: [ "authors.name = ? AND posts.active = ?", "Barack Obama", true ])

2

Page 7: Advanced Arel: When ActiveRecord Just Isn't Enough

RAISE YOUR HAND IF...

Posts .joins( "JOIN comments ON comments.post_id = posts.id", "JOIN authors ON authors.id = comments.author_id") .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

3/4

Page 8: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 9: Advanced Arel: When ActiveRecord Just Isn't Enough

RAISE YOUR HAND IF...

Posts .joins( "JOIN comments ON comments.post_id = posts.id", "JOIN authors ON authors.id = comments.author_id") .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

3/4

Page 10: Advanced Arel: When ActiveRecord Just Isn't Enough

HMM, LET’S SIMPLIFY?

Posts .joins(:comments) .joins( "JOIN authors ON authors.id = comments.author_id" ) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

3/4

Page 11: Advanced Arel: When ActiveRecord Just Isn't Enough

HMM, LET’S SIMPLIFY?

Posts .joins(:comments) .joins(:comments => :author) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

3/4

Page 12: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 13: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

3/4Posts .joins(:comments) .joins( "JOIN authors ON authors.id = comments.author_id" ) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

Page 14: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.joins( "JOIN authors ON authors.id = comments.author_id" )

Page 15: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.joins( "JOIN authors ON authors.id = comments.author_id" )

HAVE TO WRITE “JOIN” AND “ON”

Page 16: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.joins( "JOIN authors ON authors.id = comments.author_id" )

HAVE TO WRITE “JOIN” AND “ON”

HAVE TO KNOW MYSQL SYNTAX

Page 17: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.joins( "JOIN authors ON authors.id = comments.author_id" )

HAVE TO WRITE “JOIN” AND “ON”

HAVE TO KNOW MYSQL SYNTAX

NO SYNTAX CHECKING

Page 18: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

3/4Posts .joins(:comments) .joins( "JOIN authors ON authors.id = comments.author_id" ) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )

Page 19: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.where( "authors.name = ? AND posts.active = ?", "Barack Obama", true)

Page 20: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.where( "authors.name = ? AND posts.active = ?", "Barack Obama", true)

HAVE TO KNOW MYSQL SYNTAX

Page 21: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.where( "authors.name = ? AND posts.active = ?", "Barack Obama", true)

HAVE TO KNOW MYSQL SYNTAX

CONFUSING TO MATCH ARGUMENTS WITH QUESTION MARKS

Page 22: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.where( "authors.name = ? AND posts.active = ?", "Barack Obama", true)

HAVE TO KNOW MYSQL SYNTAX

CONFUSING TO MATCH ARGUMENTS WITH QUESTION MARKS

NOT OBJECT-ORIENTED

Page 23: Advanced Arel: When ActiveRecord Just Isn't Enough

PROBLEMS

.where( "authors.name = ? AND posts.active = ?", "Barack Obama", true)

HAVE TO KNOW MYSQL SYNTAX

CONFUSING TO MATCH ARGUMENTS WITH QUESTION MARKS

NOT OBJECT-ORIENTED

NO SYNTAX CHECKING

Page 24: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 25: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

Page 26: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

Page 27: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

BLOGS

Page 28: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

BLOGS

COWORKERS

Page 29: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

BLOGS

COWORKERS

FRIENDS

Page 30: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

BLOGS

COWORKERS

FRIENDS

FAMILY MEMBERS

Page 31: Advanced Arel: When ActiveRecord Just Isn't Enough

STACKOVERFLOW

RAILSCASTS #202

BLOGS

COWORKERS

FRIENDS

FAMILY MEMBERS

YOUR CAT

Page 32: Advanced Arel: When ActiveRecord Just Isn't Enough

KEEP CALMANDAVOID

LITERAL STRINGS IN

YOUR QUERIES

Page 33: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

Page 34: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

DON’T HAVE TO KNOW SQL SYNTAX

Page 35: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

DON’T HAVE TO KNOW SQL SYNTAX

RUBY SYNTAX CHECKING

Page 36: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

DON’T HAVE TO KNOW SQL SYNTAX

RUBY SYNTAX CHECKING

OBJECT-ORIENTED (CHAINABLE)

Page 37: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

DON’T HAVE TO KNOW SQL SYNTAX

RUBY SYNTAX CHECKING

OBJECT-ORIENTED (CHAINABLE)

NO QUESTION MARKS

Page 38: Advanced Arel: When ActiveRecord Just Isn't Enough

A BETTER WAY

Post .joins(:comments) .joins(Comment.joins(:author).join_sources) .where( Author[:name].eq("Barack Obama") .and(Post[:active].eq(true)) )

DON’T HAVE TO KNOW SQL SYNTAX

RUBY SYNTAX CHECKING

OBJECT-ORIENTED (CHAINABLE)

NO QUESTION MARKS

EASY TO READ - IT’S JUST RUBY!

Page 39: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT WE’LL COVER

ACTIVERECORD VS AREL

TABLES, COLUMNS

TERMINAL METHODS

SELECT, WHERE, JOIN, JOIN ASSOCIATION, ORDER

AND, OR , GREATER/LESS THAN, NOT EQUALS, ETC

MATCH, IN

Page 40: Advanced Arel: When ActiveRecord Just Isn't Enough

ACTIVERECORD

DATABASE ABSTRACTION

NO NEED TO SPEAK A DIALECT OF SQL

PERSISTENCE

DATABASE ROWS AS RUBY OBJECTS

DOMAIN LOGIC

MODELS CONTAIN APPLICATION LOGIC, VALIDATIONS, ETC

MODELS DEFINE ASSOCIATIONS

Page 41: Advanced Arel: When ActiveRecord Just Isn't Enough

AREL

“RELATIONAL ALGEBRA” FOR RUBY

BUILDS SQL QUERIES, GENERATES ASTS

APPLIES QUERY OPTIMIZATIONS

ENABLES CHAINING

“VEXINGLY UNDOCUMENTED”

Page 42: Advanced Arel: When ActiveRecord Just Isn't Enough

AREL

KNOWS NOTHING ABOUT YOUR MODELS

KNOWS VERY LITTLE ABOUT YOUR DATABASE

DOES NOT RETRIEVE OR STORE DATA

Page 43: Advanced Arel: When ActiveRecord Just Isn't Enough

AREL

KNOWS NOTHING ABOUT YOUR MODELS

KNOWS VERY LITTLE ABOUT YOUR DATABASE

DOES NOT RETRIEVE OR STORE DATA

ACTIVERECORD’S RESPONSIBILITY

Page 44: Advanced Arel: When ActiveRecord Just Isn't Enough

AREL

CONSTRUCTS QUERIES

Page 45: Advanced Arel: When ActiveRecord Just Isn't Enough

ACTIVERECORD

DOES EVERYTHING ELSE

Page 46: Advanced Arel: When ActiveRecord Just Isn't Enough

HIERARCHY

activerecord

arel

database

Page 47: Advanced Arel: When ActiveRecord Just Isn't Enough

activerecord

arel

database

CONSTRUCT QUERY

EXECUTE QUERY

HIERARCHY

Page 48: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT’S AN AST?

7

4 9

1 5

Page 49: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT’S AN AST?

7

4 9

1 5

LEFT CHILD

Page 50: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT’S AN AST?

7

4 9

1 5

LEFT CHILD RIGHT CHILD

Page 51: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT’S AN AST?

5 * (6 + 3)

*

5 +

6 3

Page 52: Advanced Arel: When ActiveRecord Just Isn't Enough

WHAT’S AN AST?

SELECT id, text FROM posts

SELECT

id text

<query>

FROM

posts

Page 53: Advanced Arel: When ActiveRecord Just Isn't Enough

LET’S GET TO SOME CODE

Page 54: Advanced Arel: When ActiveRecord Just Isn't Enough

AREL-HELPERS GEM

gem install arel-helpersgem “arel-helpers”, “~> 1.1.0”

Page 55: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base has_many :commentsend

Page 56: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base has_many :commentsend

Post.arel_table[:id]

Page 57: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base has_many :commentsend

Post.arel_table[:id]Post.arel_table[:text]

Page 58: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base has_many :commentsend

Post.arel_table[:id]Post.arel_table[:text]

=> #<struct Arel::Attributes::Attribute ... >

Page 59: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base include ArelHelpers::ArelTable has_many :commentsend

Post.arel_table[:id]Post.arel_table[:text]

=> #<struct Arel::Attributes::Attribute ... >

Page 60: Advanced Arel: When ActiveRecord Just Isn't Enough

TABLES AND COLUMNS

class Post < ActiveRecord::Base include ArelHelpers::ArelTable has_many :commentsend

Post[:id]Post[:text]

=> #<struct Arel::Attributes::Attribute ... >

Page 61: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

POP QUIZ! WHAT DOES THIS STATEMENT RETURN?

Post.select(:title)

Page 62: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

POP QUIZ! WHAT DOES THIS STATEMENT RETURN?

Post.select(:title)A. [“Rails is Cool” ... ]

Page 63: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

POP QUIZ! WHAT DOES THIS STATEMENT RETURN?

Post.select(:title)A. [“Rails is Cool” ... ]B. [#<Post title=”Rails is Cool”>, ... ]

Page 64: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

POP QUIZ! WHAT DOES THIS STATEMENT RETURN?

Post.select(:title)A. [“Rails is Cool” ... ]

C. <ActiveRecord::Relation ... >B. [#<Post title=”Rails is Cool”>, ... ]

Page 65: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

POP QUIZ! WHAT DOES THIS STATEMENT RETURN?

Post.select(:title)A. [“Rails is Cool” ... ]

C. <ActiveRecord::Relation ... >B. [#<Post title=”Rails is Cool”>, ... ]

Page 66: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

query = Post.select(:title)

Page 67: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

query = Post.select(:title)query = query.select(:id)

Page 68: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

query = Post.select(:title)

query.to_sqlquery = query.select(:id)

Page 69: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

query = Post.select(:title)

query.to_sql=> SELECT title, id FROM `posts`

query = query.select(:id)

Page 70: Advanced Arel: When ActiveRecord Just Isn't Enough

RELATIONS

query = Post.select(:title)

query.to_sql=> SELECT title, id FROM `posts`

query = query.select(:id)

RELATIONS CAN BE CHAINED!

Page 71: Advanced Arel: When ActiveRecord Just Isn't Enough

THE SERENDIPITY OF “SELECT”

Page 72: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select([:id, :text]).to_sql=> SELECT id, text FROM `posts`

Page 73: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select([:id, :text]).to_sql=> SELECT id, text FROM `posts`

Post.select(:id).count.to_sql=> NoMethodError: undefined method `to_sql' for 26:Fixnum

Page 74: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select([:id, :text]).to_sql=> SELECT id, text FROM `posts`

Post.select(:id).count.to_sql=> NoMethodError: undefined method `to_sql' for 26:Fixnum

WHAT HAPPENED??

Page 75: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select([:id, :text]).to_sql=> SELECT id, text FROM `posts`

Post.select(:id).count.to_sql=> NoMethodError: undefined method `to_sql' for 26:Fixnum

WHAT HAPPENED??

.count IS A TERMINAL METHOD

Page 76: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select([Post[:id].count, :text]).to_sql=> SELECT COUNT(`posts`.`id`), text FROM `posts`

Page 77: Advanced Arel: When ActiveRecord Just Isn't Enough

TERMINAL METHODS

EXECUTE IMMEDIATELY

DO NOT RETURN AN ActiveRecord::Relation

count

first, last

to_a

pluck

each, map, ETC

Page 78: Advanced Arel: When ActiveRecord Just Isn't Enough

TERMINAL METHODS

Post.where(title: "Arel is Cool").each do |post| puts post.textend

Page 79: Advanced Arel: When ActiveRecord Just Isn't Enough

TERMINAL METHODS

Post.where(title: "Arel is Cool").each do |post| puts post.textend

Post.where(title: "Arel is Cool").each_slice(3)

Page 80: Advanced Arel: When ActiveRecord Just Isn't Enough

TERMINAL METHODS

Post.where(title: "Arel is Cool").each do |post| puts post.textend

Post.where(title: "Arel is Cool").each_slice(3)

BOTH EXECUTE THE QUERY IMMEDIATELY

Page 81: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select(Post[:visitors].sum).to_sql=> SELECT SUM(`posts`.`visitors`) AS sum_id FROM `posts`

Page 82: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select(Post[:visitors].sum.as("visitor_total")).to_sql=> SELECT SUM(`posts`.`views`) AS visitor_total FROM `posts`

Page 83: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select(Post[:visitors].sum).to_sql=> SELECT SUM(`posts`.`visitors`) AS sum_id FROM `posts`

Post.select(Post[:visitors].maximum).to_sql=> SELECT MAX(`posts`.`visitors`) AS max_id FROM `posts`

Page 84: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select(Post[:visitors].sum).to_sql=> SELECT SUM(`posts`.`visitors`) AS sum_id FROM `posts`

Post.select(Post[:visitors].maximum).to_sql=> SELECT MAX(`posts`.`visitors`) AS max_id FROM `posts`

Post.select(Post[:visitors].minimum).to_sql=> SELECT MIN(`posts`.`visitors`) AS min_id FROM `posts`

Page 85: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select( Arel::Nodes::NamedFunction.new( "LENGTH", [Post[:text]] ).as("length")).to_sql

=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`

Page 86: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select( Arel::Nodes::NamedFunction.new( "LENGTH", [Post[:text]] ).as("length")).to_sql

=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`

Page 87: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select( Arel::Nodes::NamedFunction.new( "LENGTH", [Post[:text]] ).as("length")).to_sql

=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`

include Arel::Nodes

Page 88: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select( NamedFunction.new( "LENGTH", [Post[:text]] ).as("length")).to_sql

=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`

include Arel::Nodes

Page 89: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT

Post.select(Arel.star).to_sql=> SELECT * FROM `posts`

Page 90: Advanced Arel: When ActiveRecord Just Isn't Enough

SELECT FROM

Post.select(:id).from(Post.select([:id, :text]).ast).to_sql=> SELECT id FROM SELECT id, text FROM `posts`

Page 91: Advanced Arel: When ActiveRecord Just Isn't Enough

THE WONDER OF “WHERE”

Page 92: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(title: "Arel is Cool").to_sql=> SELECT `users`.* FROM `users` WHERE `users`.`title` = 'Arel is Cool'

WITH ACTIVERECORD SUGAR

Page 93: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(title: "Arel is Cool").to_sql=> SELECT `users`.* FROM `users` WHERE `users`.`title` = 'Arel is Cool'

WITH ACTIVERECORD SUGAR

Post.where(Post[:title].eq("Arel is Cool")).to_sql=> SELECT `users`.* FROM `users` WHERE `users`.`title` = 'Arel is Cool'

WITH AREL

Page 94: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(Post[:title].not_eq("Arel is Cool")).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`title` != 'Arel is Cool')

Page 95: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(Post[:title].not_eq("Arel is Cool")).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`title` != 'Arel is Cool')

Post.where(Post[:title].not_eq(nil)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`title` IS NOT NULL)

Page 96: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(Post[:visitors].gt(250)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` > 250)

Page 97: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(Post[:visitors].gt(250)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` > 250)

Post.where(Post[:visitors].lt(250)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` < 250)

Page 98: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where(Post[:visitors].gteq(250)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` >= 250)

Post.where(Post[:visitors].lteq(250)).to_sql=> SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` <= 250)

Page 99: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where( Post[:title].eq("Arel is Cool").and( Post[:id].eq(22).or( Post[:id].eq(23) ) )).to_sql=> SELECT `posts`.* FROM `posts` WHERE ( `posts`.`title` = 'Arel is Cool' AND (`posts`.`id` = 22 OR `posts`.`id` = 23) )

Page 100: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where( Post[:title].eq("Arel is Cool").and( Post[:id].in(22, 23) )).to_sql

=> SELECT `posts`.* FROM `posts` WHERE ( `posts`.`title` = 'Arel is Cool' AND `posts`.`id` IN (22, 23) )

Page 101: Advanced Arel: When ActiveRecord Just Isn't Enough

WHERE

Post.where( Post[:title].eq("Arel is Cool").and( NamedFunction.new("LENGTH", [Post[:slug]]).gt(10) )).to_sql

=> SELECT `posts`.* FROM `posts` WHERE ( `posts`.`title` = 'Arel is Cool' AND LENGTH(`posts`.`slug`) > 10 )

Page 102: Advanced Arel: When ActiveRecord Just Isn't Enough

THE JOY OF “JOIN”

Page 103: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

class Post < ActiveRecord::Base has_many :commentsend

class Comment < ActiveRecord::Base belongs_to :post has_one :authorend

class Author < ActiveRecord::Base belongs_to :commentend

Page 104: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author.joins(:comment).where(id: 42).to_sql

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` WHERE `authors`.`id` = 42

Page 105: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(:comment, :comment => :post) .where(Post[:id].eq(42)) .to_sql

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` INNER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 106: Advanced Arel: When ActiveRecord Just Isn't Enough

WAIT, WHAT ABOUT OUTER JOINS?

Page 107: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(:comment, :comment => :post) .where(Post[:id].eq(42)) .to_sql

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` INNER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 108: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(:comment) .joins(Comment.joins(:post).join_sources) .where(Post[:id].eq(42)) .to_sql

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` INNER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 109: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(Author.joins(:comment).join_sources) .joins(Comment.joins(:post).join_sources) .where(Post[:id].eq(42)) .to_sql

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` INNER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 110: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( Author.arel_table.join(Comment.arel_table) .on(Comment[:id].eq(Author[:comment_id])) .join_sources ) .joins( Comment.arel_table.join(Post.arel_table) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

Page 111: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( Author.arel_table.join(Comment.arel_table, Arel::OuterJoin) .on(Comment[:id].eq(Author[:comment_id])) .join_sources ) .joins( Comment.arel_table.join(Post.arel_table, Arel::OuterJoin) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

Page 112: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

=> SELECT `authors`.* FROM `authors` LEFT OUTER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` LEFT OUTER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 113: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 114: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( Author.arel_table.join(Comment.arel_table, Arel::OuterJoin) .on(Comment[:id].eq(Author[:comment_id])) .join_sources ) .joins( Comment.arel_table.join(Post.arel_table, Arel::OuterJoin) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

Page 115: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( Author.arel_table.join(Comment.arel_table, Arel::OuterJoin) .on(Comment[:id].eq(Author[:comment_id])) .join_sources ) .joins( Comment.arel_table.join(Post.arel_table, Arel::OuterJoin) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

Page 116: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( Author.arel_table.join(Comment.arel_table, Arel::OuterJoin) .on(Comment[:id].eq(Author[:comment_id])) .join_sources ) .joins( Comment.arel_table.join(Post.arel_table, Arel::OuterJoin) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

include ArelHelpers::JoinAssociation

Page 117: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(join_association(Author, :comment, Arel::OuterJoin)) .joins( Comment.arel_table.join(Post.arel_table, Arel::OuterJoin) .on(Post[:id].eq(Comment[:post_id])) .join_sources ) .where(Post[:id].eq(42)) .to_sql

include ArelHelpers::JoinAssociation

Page 118: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins(join_association(Author, :comment, Arel::OuterJoin)) .joins(join_association(Comment, :post, Arel::OuterJoin)) .where(Post[:id].eq(42)) .to_sql

include ArelHelpers::JoinAssociation

Page 119: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 120: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

Author .joins( join_association(Author, :comment) do |assoc_name, join_conds| join_conds.and(Comment[:created_at].lteq(Date.yesterday)) end ) .joins(join_association(Comment, :post, Arel::OuterJoin)) .where(Post[:id].eq(42)) .to_sql

include ArelHelpers::JoinAssociation

Page 121: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN

=> SELECT `authors`.* FROM `authors` INNER JOIN `comments` ON `comments`.`id` = `authors`.`comment_id` AND `comments`.`created_at` <= '2014-04-15' LEFT OUTER JOIN `posts` ON `posts`.`id` = `comments`.`post_id` WHERE `posts`.`id` = 42

Page 122: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

class Course < ActiveRecord::Base has_and_belongs_to_many :teachersend

class Teacher < ActiveRecord::Base has_and_belongs_to_many :coursesend

Page 123: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

class Course < ActiveRecord::Base has_and_belongs_to_many :teachersend

class Teacher < ActiveRecord::Base has_and_belongs_to_many :coursesend

coursesteacherscourses_teachers

Page 124: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Page 125: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

course TABLE

Page 126: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Course.arel_table

course TABLE

Page 127: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Course.arel_table

course TABLE

teacher TABLE

Page 128: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Course.arel_table

Teacher.arel_table

course TABLE

teacher TABLE

Page 129: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Course.arel_table

Teacher.arel_table

course TABLE

teacher TABLE

courses_teachers TABLE

Page 130: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Course.arel_table

Teacher.arel_table

course TABLE

teacher TABLE

???? (no model class)

courses_teachers TABLE

Page 131: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

Page 132: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

ct = Arel::Table.new(:courses_teachers)

Page 133: Advanced Arel: When ActiveRecord Just Isn't Enough

JOIN TABLES

ct = Arel::Table.new(:courses_teachers)

Course .joins( Course.arel_table.join(Teacher.arel_table) .on(Course[:id].eq(ct[:course_id])) .and(Teacher[:id].eq(ct[:teacher_id])) .join_sources ) .to_sql

Page 134: Advanced Arel: When ActiveRecord Just Isn't Enough

THE OPULENCE OF “ORDER”

Page 135: Advanced Arel: When ActiveRecord Just Isn't Enough

ORDER

Post.order(:visitors).to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors

Page 136: Advanced Arel: When ActiveRecord Just Isn't Enough

ORDER

Post.order(:visitors).to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors

Post.order(:views).reverse_order.to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors DESC

Page 137: Advanced Arel: When ActiveRecord Just Isn't Enough

ORDER

Post.order(:visitors).to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors

Post.order(:views).reverse_order.to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors DESC

Post.order(Post[:views].desc).to_sql=> SELECT `posts`.* FROM `posts` ORDER BY visitors DESC

Page 138: Advanced Arel: When ActiveRecord Just Isn't Enough

SUBQUERIES WITH “IN”

Page 139: Advanced Arel: When ActiveRecord Just Isn't Enough

IN

Post.where( Post.arel_table[:title].in( Post.select(:title).where(id: 5).ast )).to_sql

=> SELECT `phrases`.* FROM `phrases` WHERE `phrases`.`title` IN ( SELECT title FROM `phrases` WHERE `phrases`.`id` = 5 )

Page 140: Advanced Arel: When ActiveRecord Just Isn't Enough

LIKE QUERIES WITH “MATCHES”

Page 141: Advanced Arel: When ActiveRecord Just Isn't Enough

MATCHES

Post.where(Post[:title].matches("%arel%")).to_sql=> SELECT `phrases`.* FROM `phrases` WHERE (`phrases`.`key` LIKE x'256172656c25')

Page 142: Advanced Arel: When ActiveRecord Just Isn't Enough

QUERY BUILDERS

Page 143: Advanced Arel: When ActiveRecord Just Isn't Enough

class QueryBuilder extend Forwardable attr_reader :query def_delegators :@query, :to_a, :to_sql, :each

def initialize(query) @query = query end

protected

def reflect(query) self.class.new(query) endend

Page 144: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 145: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 146: Advanced Arel: When ActiveRecord Just Isn't Enough

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) )end

Page 147: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 148: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 149: Advanced Arel: When ActiveRecord Just Isn't Enough

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) )end

Page 150: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 151: Advanced Arel: When ActiveRecord Just Isn't Enough

class PostQueryBuilder < QueryBuilder def initialize(query = nil) super(query || post.unscoped) end

def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end

def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end

def since_yesterday reflect( query.where(post[:created_at].gteq(Date.yesterday)) ) end

private

def author Author end

def post Post endend

Page 152: Advanced Arel: When ActiveRecord Just Isn't Enough

def since_yesterday reflect( query.where( post[:created_at].gteq(Date.yesterday) ) )end

Page 153: Advanced Arel: When ActiveRecord Just Isn't Enough

EXAMPLES

PostQueryBuilder.new .with_comments_by(['camertron', 'catwithtail']) .to_sql

=> SELECT `posts`.* FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id` INNER JOIN `authors` ON `authors`.`comment_id` = `comments`.`id` WHERE `authors`.`username` IN ( 'camertron', 'catwithtail' )

Page 154: Advanced Arel: When ActiveRecord Just Isn't Enough

EXAMPLES

PostQueryBuilder.new .with_comments_by(['camertron', 'catwithtail']) .with_title_matching("arel").to_sql

=> SELECT `posts`.* FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id` INNER JOIN `authors` ON `authors`.`comment_id` = `comments`.`id` WHERE `authors`.`username` IN ( 'camertron', 'catwithtail' ) AND (`posts`.`title` LIKE '%arel%')

Page 155: Advanced Arel: When ActiveRecord Just Isn't Enough

EXAMPLES

PostQueryBuilder.new .with_comments_by(['camertron', 'catwithtail']) .with_title_matching(`arel`).since_yesterday.to_sql

=> SELECT `posts`.* FROM `posts` INNER JOIN `comments` ON `comments`.`post_id` = `posts`.`id` INNER JOIN `authors` ON `authors`.`comment_id` = `comments`.`id` WHERE `authors`.`username` IN ( 'camertron', 'catwithtail' ) AND (`posts`.`title` LIKE '%arel%') AND (`posts`.`created_at` >= '2014-04-20')

Page 156: Advanced Arel: When ActiveRecord Just Isn't Enough

Sigh... this is all so complicated. Surely there’s a tool out there...“

Page 157: Advanced Arel: When ActiveRecord Just Isn't Enough

SCUTTLEwww.scuttle.io

Page 158: Advanced Arel: When ActiveRecord Just Isn't Enough
Page 159: Advanced Arel: When ActiveRecord Just Isn't Enough

SCUTTLE

sql-parser (java)http://github.com/camertron/sql-parser

scuttle-rb (jRuby gem)http://github.com/camertron/scuttle-rb

scuttle-server (sinatra jRuby)http://github.com/camertron/scuttle-server

Page 160: Advanced Arel: When ActiveRecord Just Isn't Enough

THANK YOU!