บทความนี้มีในภาษาเกาหลีด้วย ขอบคุณ Soonsang Hong!
ขอบเขตของ Rails ทำให้ง่ายต่อการค้นหาบันทึกที่คุณต้องการ:
class Review < ActiveRecord::Base
belongs_to :restaurant
scope :positive, -> { where("rating > 3.0") }
end
irb(main):001:0> Restaurant.first.reviews.positive.count
Restaurant Load (0.4ms) SELECT `restaurants`.* FROM `restaurants` ORDER BY `restaurants`.`id` ASC LIMIT 1
(0.6ms) SELECT COUNT(*) FROM `reviews` WHERE `reviews`.`restaurant_id` = 1 AND (rating > 3.0)
=> 5
แต่ถ้าคุณไม่ระวัง คุณจะส่งผลเสียต่อประสิทธิภาพของแอปอย่างมาก
ทำไม คุณไม่สามารถโหลดขอบเขตล่วงหน้าได้จริงๆ ดังนั้น หากคุณพยายามแสดงร้านอาหารสองสามร้านที่มีรีวิวในเชิงบวก:
irb(main):001:0> restauraunts = Restaurant.first(5)
irb(main):002:0> restauraunts.map do |restaurant|
irb(main):003:1* "#{restaurant.name}: #{restaurant.reviews.positive.length} positive reviews."
irb(main):004:1> end
Review Load (0.6ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`restaurant_id` = 1 AND (rating > 3.0)
Review Load (0.5ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`restaurant_id` = 2 AND (rating > 3.0)
Review Load (0.7ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`restaurant_id` = 3 AND (rating > 3.0)
Review Load (0.7ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`restaurant_id` = 4 AND (rating > 3.0)
Review Load (0.7ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`restaurant_id` = 5 AND (rating > 3.0)
=> ["Judd's Pub: 5 positive reviews.", "Felix's Nightclub: 6 positive reviews.", "Mabel's Burrito Shack: 7 positive reviews.", "Kendall's Burrito Shack: 2 positive reviews.", "Elisabeth's Deli: 15 positive reviews."]
ใช่ นั่นคือแบบสอบถาม N+1 สาเหตุที่ใหญ่ที่สุดของแอป Rails ที่ช้า
คุณสามารถแก้ไขได้ค่อนข้างง่าย ถ้าคุณคิดเกี่ยวกับความสัมพันธ์ในวิธีที่แตกต่างออกไป
แปลงขอบเขตเป็นการเชื่อมโยง
เมื่อคุณใช้วิธีการเชื่อมโยง Rails เช่น belongs_to
และ has_many
, โมเดลของคุณมักจะมีลักษณะดังนี้:
class Restaurant < ActiveRecord::Base
has_many :reviews
end
แต่ถ้าคุณดูเอกสารประกอบ คุณจะเห็นว่าพวกเขาทำอะไรได้มากกว่านั้น คุณสามารถส่งพารามิเตอร์อื่นๆ ไปยังเมธอดเหล่านั้นและเปลี่ยนแปลงวิธีการทำงาน
scope
เป็นหนึ่งในสิ่งที่มีประโยชน์มากที่สุด ทำงานเหมือนกับ scope
จากก่อนหน้านี้:
class Restaurant < ActiveRecord::Base
has_many :reviews
has_many :positive_reviews, -> { where("rating > 3.0") }, class_name: "Review"
end
irb(main):001:0> Restaurant.first.positive_reviews.count
Restaurant Load (0.2ms) SELECT `restaurants`.* FROM `restaurants` ORDER BY `restaurants`.`id` ASC LIMIT 1
(0.4ms) SELECT COUNT(*) FROM `reviews` WHERE `reviews`.`restaurant_id` = 1 AND (rating > 3.0)
=> 5
ตอนนี้คุณสามารถโหลดการเชื่อมโยงใหม่ของคุณล่วงหน้าด้วย includes
:
irb(main):001:0> restauraunts = Restaurant.includes(:positive_reviews).first(5)
Restaurant Load (0.3ms) SELECT `restaurants`.* FROM `restaurants` ORDER BY `restaurants`.`id` ASC LIMIT 5
Review Load (1.2ms) SELECT `reviews`.* FROM `reviews` WHERE (rating > 3.0) AND `reviews`.`restaurant_id` IN (1, 2, 3, 4, 5)
irb(main):002:0> restauraunts.map do |restaurant|
irb(main):003:1* "#{restaurant.name}: #{restaurant.positive_reviews.length} positive reviews."
irb(main):004:1> end
=> ["Judd's Pub: 5 positive reviews.", "Felix's Nightclub: 6 positive reviews.", "Mabel's Burrito Shack: 7 positive reviews.", "Kendall's Burrito Shack: 2 positive reviews.", "Elisabeth's Deli: 15 positive reviews."]
แทนที่จะเรียกใช้ SQL 6 ครั้ง เราทำแค่สองครั้งเท่านั้น
(โดยใช้ class_name
คุณสามารถมีการเชื่อมโยงหลายรายการกับวัตถุเดียวกันได้ สิ่งนี้มีประโยชน์ค่อนข้างบ่อย)
แล้วการทำสำเนาล่ะ
ยังอาจมีปัญหาที่นี่ where("rating > 3.0")
อยู่ในชั้นเรียนร้านอาหารของคุณแล้ว หากคุณเปลี่ยนความเห็นเชิงบวกเป็น rating > 3.5
คุณต้องอัปเดตสองครั้ง!
แย่ลงไปอีก:หากคุณต้องการได้รับรีวิวเชิงบวกทั้งหมดที่บุคคลเคยทิ้งไว้ คุณจะต้องทำซ้ำขอบเขตนั้นในคลาสผู้ใช้ด้วย:
class User < ActiveRecord::Base
has_many :reviews
has_many :positive_reviews, -> { where("rating > 3.0") }, class_name: "Review"
end
ไม่แห้งมาก
มีวิธีง่าย ๆ ในเรื่องนี้ ภายใน where
คุณสามารถใช้ positive
ขอบเขตที่คุณเพิ่มในคลาสรีวิว:
class Restaurant < ActiveRecord::Base
has_many :reviews
has_many :positive_reviews, -> { positive }, class_name: "Review"
end
ด้วยวิธีนี้ ความคิด ของสิ่งที่ทำให้รีวิวเป็นรีวิวเชิงบวกยังคงอยู่ในที่เดียว
ขอบเขตที่ดี ในสถานที่ที่เหมาะสม พวกเขาสามารถทำให้การสืบค้นข้อมูลของคุณเป็นเรื่องง่ายและสนุก แต่ถ้าคุณต้องการหลีกเลี่ยงข้อความค้นหา N+1 คุณต้องระวังด้วย
ดังนั้น หากขอบเขตเริ่มสร้างปัญหาให้กับคุณ ให้รวมเข้าด้วยกันและโหลดไว้ล่วงหน้า . มันไม่ได้ผลมากนัก และมันจะช่วยให้คุณประหยัดการเรียก SQL ได้มากมาย