SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
Advanced Arel: When ActiveRecord Just Isn't Enough
17.
PROBLEMS
.where(
"authors.name = ? AND posts.active = ?",
"Barack Obama", true
)
HAVE TO KNOW MYSQL SYNTAX
18.
PROBLEMS
.where(
"authors.name = ? AND posts.active = ?",
"Barack Obama", true
)
HAVE TO KNOW MYSQL SYNTAX
CONFUSING TO MATCH ARGUMENTS WITH
QUESTION MARKS
19.
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
20.
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
26.
STACKOVERFLOW
RAILSCASTS #202
BLOGS
COWORKERS
FRIENDS
FAMILY MEMBERS
27.
STACKOVERFLOW
RAILSCASTS #202
BLOGS
COWORKERS
FRIENDS
FAMILY MEMBERS
YOUR CAT
28.
KEEP CALM
AND
AVOID
LITERAL
STRINGS IN
YOUR
QUERIES
29.
A BETTER WAY
Post
.joins(:comments)
.joins(Comment.joins(:author).join_sources)
.where(
Author[:name].eq("Barack Obama")
.and(Post[:active].eq(true))
)
30.
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
31.
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
32.
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)
33.
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
34.
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!
35.
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
36.
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
51.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
has_many :comments
end
52.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
has_many :comments
end
Post.arel_table[:id]
53.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
has_many :comments
end
Post.arel_table[:id]
Post.arel_table[:text]
54.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
has_many :comments
end
Post.arel_table[:id]
Post.arel_table[:text]
=> #<struct Arel::Attributes::Attribute ... >
55.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
include ArelHelpers::ArelTable
has_many :comments
end
Post.arel_table[:id]
Post.arel_table[:text]
=> #<struct Arel::Attributes::Attribute ... >
56.
TABLES AND COLUMNS
class Post < ActiveRecord::Base
include ArelHelpers::ArelTable
has_many :comments
end
Post[:id]
Post[:text]
=> #<struct Arel::Attributes::Attribute ... >
57.
RELATIONS
POP QUIZ! WHAT DOES THIS STATEMENT RETURN?
Post.select(:title)
58.
RELATIONS
POP QUIZ! WHAT DOES THIS STATEMENT RETURN?
Post.select(:title)
A. [“Rails is Cool” ... ]
59.
RELATIONS
POP QUIZ! WHAT DOES THIS STATEMENT RETURN?
Post.select(:title)
A. [“Rails is Cool” ... ]
B. [#<Post title=”Rails is Cool”>, ... ]
60.
RELATIONS
POP QUIZ! WHAT DOES THIS STATEMENT RETURN?
Post.select(:title)
A. [“Rails is Cool” ... ]
C. <ActiveRecord::Relation ... >
B. [#<Post title=”Rails is Cool”>, ... ]
61.
RELATIONS
POP QUIZ! WHAT DOES THIS STATEMENT RETURN?
Post.select(:title)
A. [“Rails is Cool” ... ]
C. <ActiveRecord::Relation ... >
B. [#<Post title=”Rails is Cool”>, ... ]
68.
SELECT
Post.select([:id, :text]).to_sql
=> SELECT id, text FROM `posts`
69.
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
70.
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??
71.
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
72.
SELECT
Post.select([Post[:id].count, :text]).to_sql
=> SELECT COUNT(`posts`.`id`), text FROM `posts`
73.
TERMINAL METHODS
EXECUTE IMMEDIATELY
DO NOT RETURN AN ActiveRecord::Relation
count
first, last
to_a
pluck
each, map, ETC
74.
TERMINAL METHODS
Post.where(title: "Arel is Cool").each do |post|
puts post.text
end
75.
TERMINAL METHODS
Post.where(title: "Arel is Cool").each do |post|
puts post.text
end
Post.where(title: "Arel is Cool").each_slice(3)
76.
TERMINAL METHODS
Post.where(title: "Arel is Cool").each do |post|
puts post.text
end
Post.where(title: "Arel is Cool").each_slice(3)
BOTH EXECUTE THE QUERY IMMEDIATELY
77.
SELECT
Post.select(Post[:visitors].sum).to_sql
=> SELECT SUM(`posts`.`visitors`) AS sum_id FROM `posts`
78.
SELECT
Post.select(Post[:visitors].sum.as("visitor_total")).to_sql
=> SELECT SUM(`posts`.`views`) AS visitor_total FROM `posts`
79.
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`
80.
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`
81.
SELECT
Post.select(
Arel::Nodes::NamedFunction.new(
"LENGTH", [Post[:text]]
).as("length")
).to_sql
=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`
82.
SELECT
Post.select(
Arel::Nodes::NamedFunction.new(
"LENGTH", [Post[:text]]
).as("length")
).to_sql
=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`
83.
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
84.
SELECT
Post.select(
NamedFunction.new(
"LENGTH", [Post[:text]]
).as("length")
).to_sql
=> SELECT LENGTH(`posts`.`text`) AS length FROM `posts`
include Arel::Nodes
85.
SELECT
Post.select(Arel.star).to_sql
=> SELECT * FROM `posts`
86.
SELECT FROM
Post.select(:id).from(Post.select([:id, :text]).ast).to_sql
=> SELECT id FROM SELECT id, text FROM `posts`
88.
WHERE
Post.where(title: "Arel is Cool").to_sql
=> SELECT `users`.* FROM `users`
WHERE `users`.`title` = 'Arel is Cool'
WITH ACTIVERECORD SUGAR
89.
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
90.
WHERE
Post.where(Post[:title].not_eq("Arel is Cool")).to_sql
=> SELECT `posts`.* FROM `posts`
WHERE (`posts`.`title` != 'Arel is Cool')
91.
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)
92.
WHERE
Post.where(Post[:visitors].gt(250)).to_sql
=> SELECT `posts`.* FROM `posts`
WHERE (`posts`.`visitors` > 250)
93.
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)
94.
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)
95.
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)
)
96.
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)
)
97.
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
)
99.
JOIN
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
has_one :author
end
class Author < ActiveRecord::Base
belongs_to :comment
end
100.
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
101.
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
108.
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
114.
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
115.
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
116.
JOIN TABLES
class Course < ActiveRecord::Base
has_and_belongs_to_many :teachers
end
class Teacher < ActiveRecord::Base
has_and_belongs_to_many :courses
end
117.
JOIN TABLES
class Course < ActiveRecord::Base
has_and_belongs_to_many :teachers
end
class Teacher < ActiveRecord::Base
has_and_belongs_to_many :courses
end
courses
teachers
courses_teachers
129.
ORDER
Post.order(:visitors).to_sql
=> SELECT `posts`.* FROM `posts` ORDER BY visitors
130.
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
131.
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
133.
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
)
137.
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)
end
end
138.
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
end
end
139.
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
end
end
140.
def with_title_matching(title)
reflect(
query.where(post[:title].matches("%#{title}%"))
)
end
141.
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
end
end
142.
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
end
end
144.
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
end
end
145.
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
end
end
146.
def since_yesterday
reflect(
query.where(
post[:created_at].gteq(Date.yesterday)
)
)
end
147.
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'
)
148.
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%')
149.
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')
150.
Sigh... this is all so complicated.
Surely there’s a tool out there...“
”