Active Record Query Interface (2), Season 1

2,413 views

Published on

Published in: Technology, Business
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
2,413
On SlideShare
0
From Embeds
0
Number of Embeds
1,172
Actions
Shares
0
Downloads
21
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Active Record Query Interface (2), Season 1

    1. 1. The 10th Round of ROR Lab.Active RecordQuery Interface(2) April 7th, 2012 Hyoseong Choi ROR Lab.
    2. 2. Short Review ROR Lab.
    3. 3. Query Interface(1) - How to retrieve data from DB -
    4. 4. Bi-directional Associations :inverse_of class Customer < ActiveRecord::Base   has_many :orders, :inverse_of => :customer end   class Order < ActiveRecord::Base   belongs_to :customer, :inverse_of => :orders end --------------------------------------------------- c = Customer.first o = c.orders.first c.first_name == o.customer.first_name # => true c.first_name = Manny c.first_name == o.customer.first_name # => true ROR Lab.
    5. 5. Overriding Conditions - reorder - class Post < ActiveRecord::Base   ..   ..   has_many :comments, :order => posted_at DESC end  SELECT * FROM posts WHERE id = 10 ORDER BY nameSELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC ROR Lab.
    6. 6. Locking Records for Update• To prevent “race conditions”• To ensure “atomic updates”• Two locking mechanisms ‣ Optimistic Locking : version control ‣ Pessimistic Locking : DB lock ROR Lab.
    7. 7. Optimistic Locking• “lock_version” in DB table (default to 0)• set_locking_column to change column name c1 = Client.find(1) c2 = Client.find(1)   c1.first_name = "Michael" c1.save # increments the lock_version column   c2.name = "should fail" ROR Lab.
    8. 8. Joining Tables - Using Array/Hash of Named Associations - only with INNER JOINclass Category < ActiveRecord::Base  has_many :postsend class Post < ActiveRecord::Base • Multiple Associations  belongs_to :category  has_many :comments  has_many :tagsend Post.joins(:category, :comments) class Comment < ActiveRecord::Base  belongs_to :post  has_one :guest SELECT posts.* FROM postsend    INNER JOIN categoriesclass Guest < ActiveRecord::Base ON posts.category_id = categories.id  belongs_to :comment   INNER JOIN commentsend ON comments.post_id = posts.id class Tag < ActiveRecord::Base “return all posts that have a category and at least one comment”  belongs_to :postend ROR Lab.
    9. 9. Joining Tables - Using Array/Hash of Named Associations - only with INNER JOINclass Category < ActiveRecord::Base  has_many :postsend class Post < ActiveRecord::Base • Nested Associations(Single Level)  belongs_to :category  has_many :comments  has_many :tagsend Post.joins(:comments => :guest) class Comment < ActiveRecord::Base  belongs_to :post  has_one :guest SELECT posts.* FROM postsend    INNER JOIN commentsclass Guest < ActiveRecord::Base ON comments.post_id = posts.id  belongs_to :comment   INNER JOIN guestsend ON guests.comment_id = comments.id class Tag < ActiveRecord::Base  belongs_to :post “return all posts that have a comment made by a guest”end ROR Lab.
    10. 10. Query Interface(2) - How to retrieve data from DB -
    11. 11. Eager Loading Associations : as few queries as possibleN + 1 queries problem clients = Client.limit(10)   clients.each do |client|   puts client.address.postcode ROR Lab.
    12. 12. Eager Loading Associations : as few queries as possibleSolution to N + 1 queries problem Only 2 queries clients = Client.includes(:address).limit(10)   clients.each do |client|   puts client.address.postcodeSELECT * FROM clients LIMIT 10SELECT addresses.* FROM addresses  WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10)) ROR Lab.
    13. 13. Eager Loading Associations- Eager Loading Multiple Associations - Array of Multiple Associations Nested Associations Hash Category.includes( :posts => [{:comments => :guest}, :tags]).find(1) ROR Lab.
    14. 14. Eager Loading Associations- Conditions on Eager Loaded Associations - conditional “joins” > conditional “includes” Post.includes(:comments) .where("comments.visible", true) SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1) ROR Lab.
    15. 15. Eager Loading Associations- Conditions on Eager Loaded Associations - INNER JOIN LEFT OUTER JOIN conditional “joins” > conditional “includes” Post.includes(:comments) .where("comments.visible", true) SELECT "posts"."id" AS t0_r0, ... "comments"."updated_at" AS t1_r5 FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" WHERE (comments.visible = 1) ROR Lab.
    16. 16. Scopes• To specify ARel queries• Referenced as method calls on the association objects or model• finder methods in a scope• return an ActiveRecord::Relation object ROR Lab.
    17. 17. Scopesclass Post < ActiveRecord::Base  scope :published, where(:published => true)end ARel query ROR Lab.
    18. 18. Scopeschainableclass Post < ActiveRecord::Base  scope :published, where(:published => true).joins(:category)endchainable within scopesclass Post < ActiveRecord::Base  scope :published, where(:published => true)  scope :published_and_commented,published.and(self.arel_table[:comments_count].gt(0)) ROR Lab.
    19. 19. Scopes To call the scopePost.published # => [published posts]category = Category.firstcategory.posts.published# => [published posts belonging to this category] ROR Lab.
    20. 20. Scopes Working with timesclass Post < ActiveRecord::Base  scope :last_week, lambda { where("created_at < ?", Time.zone.now ) }end a lambda so that the scope is evaluated every time ROR Lab.
    21. 21. Scopes Passing in argumentsclass Post < ActiveRecord::Base  scope :1_week_before, lambda { |time| where("created_at < ?", time) }endclass Post < ActiveRecord::Base  def self.1_week_before(time)    where("created_at < ?", time)  end ***What about “as a class method” ? ROR Lab.
    22. 22. Scopes Passing in arguments class Post < ActiveRecord::Base   scope :1_week_before, lambda { |time| where("created_at < ?", time) } end le eab erclass Post < ActiveRecord::Base efpr   def self.1_week_before(time)     where("created_at < ?", time)   end ***What about “as a class method” ? ROR Lab.
    23. 23. ScopesPassing in arguments ROR Lab.
    24. 24. Scopes Working with Scopesclient = Client.find_by_first_name("Ryan") ROR Lab.
    25. 25. Scopes Applying a default scopeclass Client < ActiveRecord::Base  default_scope where("removed_at IS NULL")SELECT * FROM clients WHERE removed_at IS NULL ROR Lab.
    26. 26. Scopes Removing all scopes Especially useful,when escaping the default_scope ROR Lab.
    27. 27. Dynamic Finders• Model.find_by_attribute_name• Model.find_all_by_attribute_name• Model.find_last_by_attribute_name• Model.find_by_attribute_name!• Model.find_by_attr1_and_attr2Rails 3.1 < Argument Error on missing fields ROR Lab.
    28. 28. Find or build a new object• first_or_create• first_or_create! Exception on invalid• first_or_initialize ROR Lab.
    29. 29. Find or build a new object• first_or_create Client.where(:first_name => Andy).first_or_create(:locked => false) # => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27"> Client.find_or_create_by_first_name(:first_name => "Andy", :locked => false)SELECT * FROM clients WHERE (clients.first_name = Andy) LIMIT 1BEGININSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES(2011-08-30 05:22:57, Andy, 0, NULL, 2011-08-30 05:22:57)COMMIT ROR Lab.
    30. 30. Find or build a new object• first_or_create! class Client < ActiveRecord::Base validates :orders_count, :presence => true end Client.where(:first_name => Andy).first_or_create!(:locked => false) # => ActiveRecord::RecordInvalid: Validation failed: Orders count cant be blank ROR Lab.
    31. 31. Find or build a new object• first_or_initialize nick = Client.where(:first_name => Nick).first_or_initialize(:locked => false) # => <Client id: nil, first_name: "Nick", orders_count: 0, locked: false, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">   SELECT * FROM clients WHERE (clients.first_name = Nick) LIMIT 1 nick.persisted? # => false nick.new_record? # => true nick.save # => truepersisted? vs new_record? ROR Lab.
    32. 32. Finding by SQL : return an array of objects where each object indicates a recordClient.find_by_sql("SELECT * FROM clients  INNER JOIN orders ON clients.id = orders.client_idcf. connection.select_all an array of hash ROR Lab.
    33. 33. select_all : return an array of hash where each hash indicates a recordClient.connection.select_all("SELECT * FROM clients WHERE id =cf. find_by_sql -> instantiates objects ROR Lab.
    34. 34. pluck To query a single column from the underlying table of a model To return an array of valuesClient.where(:active => true).pluck(:id)# SELECT id FROM clients WHERE active = 1 Client.uniq.pluck(:role)Client.select(:id).map { |c| c.id } Client.pluck(:id) ROR Lab.
    35. 35. Existence of ObjectsClient.exists?(1)Client.exists?(1,2,3)# orClient.exists?([1,2,3])Client.where(:first_name => Ryan).exists?cf. find method ROR Lab.
    36. 36. Existence of Objects# via a modelPost.any?Post.many? # via a named scopePost.recent.any?Post.recent.many? # via a relationPost.where(:published => true).any?Post.where(:published => true).many? # via an associationPost.first.categories.any? ROR Lab.
    37. 37. Calculations Client.count # SELECT count(*) AS count_all FROM clients Client.where(:first_name => Ryan).count # SELECT count(*) AS count_all FROM clients WHERE (first_name = Ryan) Client.includes("orders").where(:first_name => Ryan, :ordersSELECT count(DISTINCT clients.id) AS count_all FROM clients  LEFT OUTER JOIN orders ON orders.client_id = client.id WHERE  (clients.first_name = Ryan AND orders.status = received) ROR Lab.
    38. 38. CalculationsClient.count(:age)Client.average("orders_count")Client.minimum("age")Client.maximum("age") ROR Lab.
    39. 39. Running EXPLAIN a pretty printing that emulates the one of the database shells User.where(:id => 1).joins(:posts).explainEXPLAIN for: SELECT `users`.* FROM `users` INNER JOIN `posts` ON `posts`.`user_id` = `users`.`id` WHERE `users`.`id`=1+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+| id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra       |+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+|  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |             ||  1 | SIMPLE      | posts | ALL   | NULL          | NULL    | NULL    | NULL  |    1 | Using where |+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+2 rows in set (0.00 sec)under MySQL ROR Lab.
    40. 40. Running EXPLAIN a pretty printing that emulates the one of the database shells User.where(:id => 1).joins(:posts).explainEXPLAIN for: SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" WHERE "users"."id" = 1                                  QUERY PLAN------------------------------------------------------------------------------ Nested Loop Left Join  (cost=0.00..37.24 rows=8 width=0)   Join Filter: (posts.user_id = users.id)   ->  Index Scan using users_pkey on users  (cost=0.00..8.27 rows=1 width=4)         Index Cond: (id = 1)   ->  Seq Scan on posts  (cost=0.00..28.88 rows=8 width=4)         Filter: (posts.user_id = 1)(6 rows)under PostgreSQL ROR Lab.
    41. 41. Running EXPLAIN Eager Loading User.where(:id => 1).includes(:posts).explain EXPLAIN for: SELECT `users`.* FROM `users`  WHERE `users`.`id` = 1 +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type  | possible_keys | key     | key_len | ref   | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ |  1 | SIMPLE      | users | const | PRIMARY       | PRIMARY | 4       | const |    1 |       | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec)   EXPLAIN for: SELECT `posts`.* FROM `posts`  WHERE `posts`.`user_id` IN (1) +----+-------------+-------+------+---------------+------+---------+------+------+------------- + | id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       | +----+-------------+-------+------+---------------+------+---------+------+------+------------- + |  1 | SIMPLE      | posts | ALL  | NULL          | NULL | NULL    | NULL |    1 | Using where |under MySQL ROR Lab.
    42. 42. Running EXPLAIN Automatic EXPLAINTo set the threshold in secondsconfig.active_record.auto_explain_threshold_in_seconds• disables automatic EXPLAIN => nil or no AR logger• Development mode => default, 0.5 sec• Test mode => nil• Production mode => nil ROR Lab.
    43. 43. Running EXPLAIN Disabling Automatic EXPLAINTo be selectively silenced with ActiveRecord::Base.silence_auto_explain do   # no automatic EXPLAIN is triggered here• useful for queries are slow but fine : a heavyweight report of a admin interface ROR Lab.
    44. 44. 감사합니다.
    45. 45.   ROR Lab.

    ×