Advanced Arel: When ActiveRecord Just Isn't Enough

13,500 views
13,240 views

Published on

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

3 Comments
34 Likes
Statistics
Notes
No Downloads
Views
Total views
13,500
On SlideShare
0
From Embeds
0
Number of Embeds
715
Actions
Shares
0
Downloads
132
Comments
3
Likes
34
Embeds 0
No embeds

No notes for slide

Advanced Arel: When ActiveRecord Just Isn't Enough

  1. 1. ADVANCED AREL WHEN ACTIVERECORD JUST ISN’T ENOUGH Author.where( name: "Cameron Dutro", username: "@camertron" )
  2. 2. `WHOAMI` CAMERON DUTRO INTERNATIONAL MAN OF MYSTERY US GOVERNMENT @CAMERTRON GITHUB.COM/CAMERTRON
  3. 3. `WHOAMI` CAMERON DUTRO INTERNATIONAL MAN OF MYSTERY US GOVERNMENT @CAMERTRON GITHUB.COM/CAMERTRON
  4. 4. `WHOAMI` CAMERON DUTRO INTERNATIONAL ENGINEERING TWITTER, INC @CAMERTRON GITHUB.COM/CAMERTRON
  5. 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. 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. 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. 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. 9. HMM, LET’S SIMPLIFY? Posts .joins(:comments) .joins(:comments => :author) .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true ) 3/4
  10. 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. 11. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" )
  12. 12. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" ) HAVE TO WRITE “JOIN” AND “ON”
  13. 13. PROBLEMS .joins( "JOIN authors ON authors.id = comments.author_id" ) HAVE TO WRITE “JOIN” AND “ON” HAVE TO KNOW MYSQL SYNTAX
  14. 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. 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. 16. PROBLEMS .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true )
  17. 17. PROBLEMS .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true ) HAVE TO KNOW MYSQL SYNTAX
  18. 18. PROBLEMS .where( "authors.name = ? AND posts.active = ?", "Barack Obama", true ) HAVE TO KNOW MYSQL SYNTAX CONFUSING TO MATCH ARGUMENTS WITH QUESTION MARKS
  19. 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. 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. 21. STACKOVERFLOW
  22. 22. STACKOVERFLOW RAILSCASTS #202
  23. 23. STACKOVERFLOW RAILSCASTS #202 BLOGS
  24. 24. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS
  25. 25. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS FRIENDS
  26. 26. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS FRIENDS FAMILY MEMBERS
  27. 27. STACKOVERFLOW RAILSCASTS #202 BLOGS COWORKERS FRIENDS FAMILY MEMBERS YOUR CAT
  28. 28. KEEP CALM AND AVOID LITERAL STRINGS IN YOUR QUERIES
  29. 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. 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. 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. 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. 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. 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. 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. 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. 37. AREL “RELATIONAL ALGEBRA” FOR RUBY BUILDS SQL QUERIES, GENERATES ASTS APPLIES QUERY OPTIMIZATIONS ENABLES CHAINING “VEXINGLY UNDOCUMENTED”
  38. 38. AREL KNOWS NOTHING ABOUT YOUR MODELS KNOWS VERY LITTLE ABOUT YOUR DATABASE DOES NOT RETRIEVE OR STORE DATA
  39. 39. AREL KNOWS NOTHING ABOUT YOUR MODELS KNOWS VERY LITTLE ABOUT YOUR DATABASE DOES NOT RETRIEVE OR STORE DATA ACTIVERECORD’S RESPONSIBILITY
  40. 40. AREL CONSTRUCTS QUERIES
  41. 41. ACTIVERECORD DOES EVERYTHING ELSE
  42. 42. HIERARCHY activerecord arel database
  43. 43. activerecord arel database CONSTRUCT QUERY EXECUTE QUERY HIERARCHY
  44. 44. WHAT’S AN AST? 7 4 9 1 5
  45. 45. WHAT’S AN AST? 7 4 9 1 5 LEFT CHILD
  46. 46. WHAT’S AN AST? 7 4 9 1 5 LEFT CHILD RIGHT CHILD
  47. 47. WHAT’S AN AST? 5 * (6 + 3) * 5 + 6 3
  48. 48. WHAT’S AN AST? SELECT id, text FROM posts SELECT id text <query> FROM posts
  49. 49. LET’S GET TO SOME CODE
  50. 50. AREL-HELPERS GEM gem install arel-helpers gem “arel-helpers”, “~> 1.1.0”
  51. 51. TABLES AND COLUMNS class Post < ActiveRecord::Base has_many :comments end
  52. 52. TABLES AND COLUMNS class Post < ActiveRecord::Base has_many :comments end Post.arel_table[:id]
  53. 53. TABLES AND COLUMNS class Post < ActiveRecord::Base has_many :comments end Post.arel_table[:id] Post.arel_table[:text]
  54. 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. 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. 56. TABLES AND COLUMNS class Post < ActiveRecord::Base include ArelHelpers::ArelTable has_many :comments end Post[:id] Post[:text] => #<struct Arel::Attributes::Attribute ... >
  57. 57. RELATIONS POP QUIZ! WHAT DOES THIS STATEMENT RETURN? Post.select(:title)
  58. 58. RELATIONS POP QUIZ! WHAT DOES THIS STATEMENT RETURN? Post.select(:title) A. [“Rails is Cool” ... ]
  59. 59. RELATIONS POP QUIZ! WHAT DOES THIS STATEMENT RETURN? Post.select(:title) A. [“Rails is Cool” ... ] B. [#<Post title=”Rails is Cool”>, ... ]
  60. 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. 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. 62. RELATIONS query = Post.select(:title)
  63. 63. RELATIONS query = Post.select(:title) query = query.select(:id)
  64. 64. RELATIONS query = Post.select(:title) query.to_sql query = query.select(:id)
  65. 65. RELATIONS query = Post.select(:title) query.to_sql => SELECT title, id FROM `posts` query = query.select(:id)
  66. 66. RELATIONS query = Post.select(:title) query.to_sql => SELECT title, id FROM `posts` query = query.select(:id) RELATIONS CAN BE CHAINED!
  67. 67. THE SERENDIPITY OF “SELECT”
  68. 68. SELECT Post.select([:id, :text]).to_sql => SELECT id, text FROM `posts`
  69. 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. 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. 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. 72. SELECT Post.select([Post[:id].count, :text]).to_sql => SELECT COUNT(`posts`.`id`), text FROM `posts`
  73. 73. TERMINAL METHODS EXECUTE IMMEDIATELY DO NOT RETURN AN ActiveRecord::Relation count first, last to_a pluck each, map, ETC
  74. 74. TERMINAL METHODS Post.where(title: "Arel is Cool").each do |post| puts post.text end
  75. 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. 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. 77. SELECT Post.select(Post[:visitors].sum).to_sql => SELECT SUM(`posts`.`visitors`) AS sum_id FROM `posts`
  78. 78. SELECT Post.select(Post[:visitors].sum.as("visitor_total")).to_sql => SELECT SUM(`posts`.`views`) AS visitor_total FROM `posts`
  79. 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. 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. 81. SELECT Post.select( Arel::Nodes::NamedFunction.new( "LENGTH", [Post[:text]] ).as("length") ).to_sql => SELECT LENGTH(`posts`.`text`) AS length FROM `posts`
  82. 82. SELECT Post.select( Arel::Nodes::NamedFunction.new( "LENGTH", [Post[:text]] ).as("length") ).to_sql => SELECT LENGTH(`posts`.`text`) AS length FROM `posts`
  83. 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. 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. 85. SELECT Post.select(Arel.star).to_sql => SELECT * FROM `posts`
  86. 86. SELECT FROM Post.select(:id).from(Post.select([:id, :text]).ast).to_sql => SELECT id FROM SELECT id, text FROM `posts`
  87. 87. THE WONDER OF “WHERE”
  88. 88. WHERE Post.where(title: "Arel is Cool").to_sql => SELECT `users`.* FROM `users` WHERE `users`.`title` = 'Arel is Cool' WITH ACTIVERECORD SUGAR
  89. 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. 90. WHERE Post.where(Post[:title].not_eq("Arel is Cool")).to_sql => SELECT `posts`.* FROM `posts` WHERE (`posts`.`title` != 'Arel is Cool')
  91. 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. 92. WHERE Post.where(Post[:visitors].gt(250)).to_sql => SELECT `posts`.* FROM `posts` WHERE (`posts`.`visitors` > 250)
  93. 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. 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. 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. 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. 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. 98. THE JOY OF “JOIN”
  99. 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. 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. 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. 102. WAIT, WHAT ABOUT OUTER JOINS?
  103. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 118. JOIN TABLES
  119. 119. JOIN TABLES course TABLE
  120. 120. JOIN TABLES Course.arel_table course TABLE
  121. 121. JOIN TABLES Course.arel_table course TABLE teacher TABLE
  122. 122. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE
  123. 123. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE courses_teachers TABLE
  124. 124. JOIN TABLES Course.arel_table Teacher.arel_table course TABLE teacher TABLE ???? (no model class) courses_teachers TABLE
  125. 125. JOIN TABLES
  126. 126. JOIN TABLES ct = Arel::Table.new(:courses_teachers)
  127. 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. 128. THE OPULENCE OF “ORDER”
  129. 129. ORDER Post.order(:visitors).to_sql => SELECT `posts`.* FROM `posts` ORDER BY visitors
  130. 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. 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. 132. SUBQUERIES WITH “IN”
  133. 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. 134. LIKE QUERIES WITH “MATCHES”
  135. 135. MATCHES Post.where(Post[:title].matches("%arel%")).to_sql => SELECT `phrases`.* FROM `phrases` WHERE (`phrases`.`key` LIKE x'256172656c25')
  136. 136. QUERY BUILDERS
  137. 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. 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. 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. 140. def with_title_matching(title) reflect( query.where(post[:title].matches("%#{title}%")) ) end
  141. 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. 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. 143. def with_comments_by(usernames) reflect( query .joins(:comments => :author) .where(Author[:username].in(usernames)) ) end
  144. 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. 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. 146. def since_yesterday reflect( query.where( post[:created_at].gteq(Date.yesterday) ) ) end
  147. 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. 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. 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. 150. Sigh... this is all so complicated. Surely there’s a tool out there...“ ”
  151. 151. SCUTTLE www.scuttle.io
  152. 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. 153. THANK YOU!

×