Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

วิธีการโหลดขอบเขต Rails ล่วงหน้า

บทความนี้มีในภาษาเกาหลีด้วย ขอบคุณ Soonsang Hong!

ขอบเขตของ Rails ทำให้ง่ายต่อการค้นหาบันทึกที่คุณต้องการ:

app/models/review.rb
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 , โมเดลของคุณมักจะมีลักษณะดังนี้:

app/models/restaurant.rb
class Restaurant < ActiveRecord::Base
  has_many :reviews
end

แต่ถ้าคุณดูเอกสารประกอบ คุณจะเห็นว่าพวกเขาทำอะไรได้มากกว่านั้น คุณสามารถส่งพารามิเตอร์อื่นๆ ไปยังเมธอดเหล่านั้นและเปลี่ยนแปลงวิธีการทำงาน

scope เป็นหนึ่งในสิ่งที่มีประโยชน์มากที่สุด ทำงานเหมือนกับ scope จากก่อนหน้านี้:

app/models/restaurant.rb
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 คุณต้องอัปเดตสองครั้ง!

แย่ลงไปอีก:หากคุณต้องการได้รับรีวิวเชิงบวกทั้งหมดที่บุคคลเคยทิ้งไว้ คุณจะต้องทำซ้ำขอบเขตนั้นในคลาสผู้ใช้ด้วย:

app/models/user.rb
class User < ActiveRecord::Base
  has_many :reviews
  has_many :positive_reviews, -> { where("rating > 3.0") }, class_name: "Review"
end

ไม่แห้งมาก

มีวิธีง่าย ๆ ในเรื่องนี้ ภายใน where คุณสามารถใช้ positive ขอบเขตที่คุณเพิ่มในคลาสรีวิว:

app/models/restaurant.rb
class Restaurant < ActiveRecord::Base
  has_many :reviews
  has_many :positive_reviews, -> { positive }, class_name: "Review"
end

ด้วยวิธีนี้ ความคิด ของสิ่งที่ทำให้รีวิวเป็นรีวิวเชิงบวกยังคงอยู่ในที่เดียว

ขอบเขตที่ดี ในสถานที่ที่เหมาะสม พวกเขาสามารถทำให้การสืบค้นข้อมูลของคุณเป็นเรื่องง่ายและสนุก แต่ถ้าคุณต้องการหลีกเลี่ยงข้อความค้นหา N+1 คุณต้องระวังด้วย

ดังนั้น หากขอบเขตเริ่มสร้างปัญหาให้กับคุณ ให้รวมเข้าด้วยกันและโหลดไว้ล่วงหน้า . มันไม่ได้ผลมากนัก และมันจะช่วยให้คุณประหยัดการเรียก SQL ได้มากมาย