ActiveRecord Query Interface (2), Season 2
Transcript of ActiveRecord Query Interface (2), Season 2
![Page 1: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/1.jpg)
Active Record Query Interface (2)
Ror lab. season 2- the 11th round -
December 1st, 2012
Hyoseong Choi
![Page 2: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/2.jpg)
Eager Loading Associations
: as few queries as possible
clients = Client.limit(10) clients.each do |client| puts client.address.postcodeend
N + 1 queries problem
![Page 3: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/3.jpg)
Eager Loading Associations
: as few queries as possible
clients = Client.includes(:address).limit(10) clients.each do |client| puts client.address.postcodeend
Solution to N + 1 queries problem
SELECT * FROM clients LIMIT 10SELECT addresses.* FROM addresses WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))
Only 2 queries
![Page 4: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/4.jpg)
Eager Loading Associations
- Eager Loading Multiple Associations -
Post.includes(:category, :comments)
Array of Multiple Associations
Nested Associations Hash
Category.includes( :posts => [{:comments => :guest}, :tags]).find(1)
![Page 5: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/5.jpg)
Eager Loading Associations
- Conditions on Eager Loaded Associations -
Post.includes(:comments) .where("comments.visible", true)
conditional “joins” > conditional “includes”
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)
![Page 6: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/6.jpg)
Eager Loading Associations
- Conditions on Eager Loaded Associations -
Post.includes(:comments) .where("comments.visible", true)
conditional “joins” > conditional “includes”
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)
LEFT OUTER JOIN
INNER JOIN LEFT OUTER JOIN
![Page 7: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/7.jpg)
Scopes
• To specify ARel queries: a SQL AST(Abstract Syntax Tree) manger for Ruby
• Referenced as method calls on the association objects or model
• finder methods in a scope
• return an ActiveRecord::Relation object
![Page 8: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/8.jpg)
Scopes
class Post < ActiveRecord::Base scope :published, where(:published => true)
end ARel query
![Page 9: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/9.jpg)
Scopesclass Post < ActiveRecord::Base scope :published, where(:published => true).joins(:category)end
chainable
class Post < ActiveRecord::Base scope :published, where(:published => true) scope :published_and_commented, published.and(self.arel_table[:comments_count].gt(0))end
chainable within scopes
![Page 10: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/10.jpg)
Scopes
Post.published # => [published posts]
category = Category.firstcategory.posts.published # => [published posts belonging to this category]
To call the scope
![Page 11: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/11.jpg)
Scopes
class Post < ActiveRecord::Base scope :last_week, lambda { where("created_at < ?", Time.zone.now ) }end
Working with times
a lambda so that the scope is evaluated every time
![Page 12: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/12.jpg)
ScopesPassing in arguments
class Post < ActiveRecord::Base scope :1_week_before, lambda { |time| where("created_at < ?", time) }end
Post.1_week_before(Time.zone.now)
class Post < ActiveRecord::Base def self.1_week_before(time) where("created_at < ?", time) endend ***What about “as a class method” ?
![Page 13: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/13.jpg)
ScopesPassing in arguments
class Post < ActiveRecord::Base scope :1_week_before, lambda { |time| where("created_at < ?", time) }end
Post.1_week_before(Time.zone.now)
class Post < ActiveRecord::Base def self.1_week_before(time) where("created_at < ?", time) endend ***What about “as a class method” ?
prefereable
![Page 14: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/14.jpg)
category.posts.1_week_before(time)
Scopes
Passing in arguments
![Page 15: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/15.jpg)
client = Client.find_by_first_name("Ryan")orders = client.orders.scoped
Scopes
Working with Scopes
orders.where("created_at > ?", 30.days.ago)
![Page 16: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/16.jpg)
class Client < ActiveRecord::Base default_scope where("removed_at IS NULL")end
Scopes
Applying a default scope
SELECT * FROM clients WHERE removed_at IS NULL
![Page 17: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/17.jpg)
Client.unscoped.all
Scopes
Removing all scopes
Especially useful, when escaping the default_scope
![Page 18: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/18.jpg)
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_attr2
Rails 3.2 => Argument Error on missing fields
![Page 19: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/19.jpg)
Find or builda new object
• first_or_create
• first_or_create!
• first_or_initialize
Exception on invalid
![Page 20: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/20.jpg)
Find or builda 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
![Page 21: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/21.jpg)
Find or builda new object
• first_or_create!
class Client < ActiveRecord::Base validates :orders_count, :presence => trueend
Client.where(:first_name => 'Andy').first_or_create!
(:locked => false)# => ActiveRecord::RecordInvalid: Validation failed: Orders
count can't be blank
![Page 22: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/22.jpg)
Find or builda 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">
nick.persisted?# => falsenick.new_record?# => truenick.save# => true
persisted? vs new_record?
SELECT * FROM clients WHERE (clients.first_name = 'Nick') LIMIT 1
![Page 23: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/23.jpg)
Finding by SQL
Client.find_by_sql("SELECT * FROM clients INNER JOIN orders ON clients.id = orders.client_id
ORDER clients.created_at desc")
: return an array of objectswhere each object indicates a record
cf. connection.select_all an array of hash
![Page 24: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/24.jpg)
select_all
Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
: return an array of hashwhere each hash indicates a record
cf. find_by_sql => instantiates objects
![Page 25: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/25.jpg)
pluckTo query a single column from the underlying table of a modelTo return an array of values
Client.where(:active => true).pluck(:id)# SELECT id FROM clients WHERE active = 1 Client.uniq.pluck(:role)# SELECT DISTINCT role FROM clients
Client.select(:id).map { |c| c.id }
Client.pluck(:id)
![Page 26: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/26.jpg)
Existence of Objects
Client.exists?(1)
Client.exists?(1,2,3)# orClient.exists?([1,2,3])
Client.where(:first_name => 'Ryan').exists?
Client.exists?
cf. find method
![Page 27: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/27.jpg)
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?Post.first.categories.many?
![Page 28: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/28.jpg)
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', :orders => {:status => 'received'}).count
SELECT 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')
![Page 29: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/29.jpg)
Calculations
Client.count(:age)
Client.average("orders_count")
Client.minimum("age")
Client.maximum("age")
Client.sum("orders_count")
![Page 30: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/30.jpg)
Running EXPLAINa pretty printing that emulates the one of the database shells
User.where(:id => 1).joins(:posts).explain
EXPLAIN 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
![Page 31: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/31.jpg)
Running EXPLAINa pretty printing that emulates the one of the database shells
User.where(:id => 1).joins(:posts).explain
EXPLAIN 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
![Page 32: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/32.jpg)
Running EXPLAINEager 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 |+----+-------------+-------+------+---------------+------+---------+------+------+-------------+1 row in set (0.00 sec)
under MySQL
![Page 33: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/33.jpg)
Running EXPLAINAutomatic EXPLAIN
config.active_record.auto_explain_threshold_in_seconds
To set the threshold in seconds
• disables automatic EXPLAIN => nil or no AR logger• Development mode => default, 0.5 sec• Test mode => nil• Production mode => nil
![Page 34: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/34.jpg)
Running EXPLAINDisabling Automatic EXPLAIN
ActiveRecord::Base.silence_auto_explain do # no automatic EXPLAIN is triggered hereend
To be selectively silenced with
• useful for queries are slow but fine : a heavyweight report of a admin interface
![Page 35: ActiveRecord Query Interface (2), Season 2](https://reader034.fdocuments.in/reader034/viewer/2022042700/554f49bfb4c905b9508b47bc/html5/thumbnails/35.jpg)
감사합니다.����������� ������������������