Your SlideShare is downloading. ×
Advanced Arel: When ActiveRecord Just Isn't Enough
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Advanced Arel: When ActiveRecord Just Isn't Enough

6,652

Published on

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

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

0 Comments
18 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
6,652
On Slideshare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
79
Comments
0
Likes
18
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. ADVANCED AREL WHEN ACTIVERECORD JUST ISN’T ENOUGH Author.where( name: "Cameron Dutro", username: "@camertron" )
  • 2. `WHOAMI` CAMERON DUTRO INTERNATIONAL MAN OF MYSTERY US GOVERNMENT @CAMERTRON GITHUB.COM/CAMERTRON
  • 3. `WHOAMI` CAMERON DUTRO INTERNATIONAL MAN OF MYSTERY US GOVERNMENT @CAMERTRON GITHUB.COM/CAMERTRON
  • 4. `WHOAMI` CAMERON DUTRO INTERNATIONAL ENGINEERING TWITTER, INC @CAMERTRON GITHUB.COM/CAMERTRON
  • 5. 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
  • 6. 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
  • 7. 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
  • 8. 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
  • 9. HMM, LET’S SIMPLIFY? Posts .joins(:comments) .joins(:comments => :author) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true ) 3/4
  • 10. PROBLEMS 3/4 Posts .joins(:comments) .joins( "JOIN authors ON authors.id = comments.author_id" ) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )
  • 11. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" )
  • 12. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" ) HAVE TO WRITE “JOIN” AND “ON”
  • 13. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" ) HAVE TO WRITE “JOIN” AND “ON” HAVE TO KNOW MYSQL SYNTAX
  • 14. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" ) HAVE TO WRITE “JOIN” AND “ON” HAVE TO KNOW MYSQL SYNTAX NO SYNTAX CHECKING
  • 15. PROBLEMS 3/4 Posts .joins(:comments) .joins( "JOIN authors ON authors.id = comments.author_id" ) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )
  • 16. PROBLEMS .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )
  • 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
  • 21. STACKOVERFLOW
  • 22. STACKOVERFLOW RAILSCASTS #202
  • 23. STACKOVERFLOW RAILSCASTS #202 BLOGS
  • 24. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS
  • 25. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS FRIENDS
  • 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
  • 37. AREL “RELATIONAL ALGEBRA” FOR RUBY BUILDS SQL QUERIES, GENERATES ASTS APPLIES QUERY OPTIMIZATIONS ENABLES CHAINING “VEXINGLY UNDOCUMENTED”
  • 38. AREL KNOWS NOTHING ABOUT YOUR MODELS KNOWS VERY LITTLE ABOUT YOUR DATABASE DOES NOT RETRIEVE OR STORE DATA
  • 39. AREL KNOWS NOTHING ABOUT YOUR MODELS KNOWS VERY LITTLE ABOUT YOUR DATABASE DOES NOT RETRIEVE OR STORE DATA ACTIVERECORD’S RESPONSIBILITY
  • 40. AREL CONSTRUCTS QUERIES
  • 41. ACTIVERECORD DOES EVERYTHING ELSE
  • 42. HIERARCHY activerecord arel database
  • 43. activerecord arel database CONSTRUCT QUERY EXECUTE QUERY HIERARCHY
  • 44. WHAT’S AN AST? 7 4 9 1 5
  • 45. WHAT’S AN AST? 7 4 9 1 5 LEFT CHILD
  • 46. WHAT’S AN AST? 7 4 9 1 5 LEFT CHILD RIGHT CHILD
  • 47. WHAT’S AN AST? 5 * (6 + 3) * 5 + 6 3
  • 48. WHAT’S AN AST? SELECT id, text FROM posts SELECT id text <query> FROM posts
  • 49. LET’S GET TO SOME CODE
  • 50. AREL-HELPERS GEM gem install arel-helpers gem “arel-helpers”, “~> 1.1.0”
  • 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”>, ... ]
  • 62. RELATIONS query = Post.select(:title)
  • 63. RELATIONS query = Post.select(:title) query = query.select(:id)
  • 64. RELATIONS query = Post.select(:title) query.to_sql query = query.select(:id)
  • 65. RELATIONS query = Post.select(:title) query.to_sql => SELECT title, id FROM `posts` query = query.select(:id)
  • 66. RELATIONS query = Post.select(:title) query.to_sql => SELECT title, id FROM `posts` query = query.select(:id) RELATIONS CAN BE CHAINED!
  • 67. THE SERENDIPITY OF “SELECT”
  • 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`
  • 87. THE WONDER OF “WHERE”
  • 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 )
  • 98. THE JOY OF “JOIN”
  • 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
  • 102. WAIT, WHAT ABOUT OUTER JOINS?
  • 103. 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
  • 104. 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
  • 105. 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
  • 106. 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
  • 107. 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
  • 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
  • 109. 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
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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
  • 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
  • 118. JOIN TABLES
  • 119. JOIN TABLES course TABLE
  • 120. JOIN TABLES Course.arel_table course TABLE
  • 121. JOIN TABLES Course.arel_table course TABLE teacher TABLE
  • 122. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE
  • 123. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE courses_teachers TABLE
  • 124. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE ???? (no model class) courses_teachers TABLE
  • 125. JOIN TABLES
  • 126. JOIN TABLES ct = Arel::Table.new(:courses_teachers)
  • 127. 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
  • 128. THE OPULENCE OF “ORDER”
  • 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
  • 132. SUBQUERIES WITH “IN”
  • 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 )
  • 134. LIKE QUERIES WITH “MATCHES”
  • 135. MATCHES Post.where(Post[:title].matches("%arel%")).to_sql => SELECT `phrases`.* FROM `phrases` WHERE (`phrases`.`key` LIKE x'256172656c25')
  • 136. QUERY BUILDERS
  • 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
  • 143. def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) 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...“ ”
  • 151. SCUTTLE www.scuttle.io
  • 152. 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
  • 153. THANK YOU!

×