Computer >> Máy Tính >  >> Lập trình >> Ruby

Cách tải trước các phạm vi Rails

Bài báo này cũng có sẵn bằng tiếng Hàn, cảm ơn Soonsang Hong!

Phạm vi của Rails giúp dễ dàng tìm thấy các bản ghi bạn muốn:

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

Nhưng nếu không cẩn thận với chúng, bạn sẽ ảnh hưởng nghiêm trọng đến hiệu suất ứng dụng của mình.

Tại sao? Bạn thực sự không thể tải trước một phạm vi. Vì vậy, nếu bạn đã cố gắng hiển thị một số nhà hàng với các đánh giá tích cực của họ:

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."]

Đúng, đó là một truy vấn N + 1. Nguyên nhân lớn nhất khiến ứng dụng Rails chạy chậm.

Tuy nhiên, bạn có thể khắc phục điều này khá dễ dàng nếu bạn nghĩ về mối quan hệ theo một cách khác.

Khi bạn sử dụng các phương thức liên kết Rails, như belongs_tohas_many , mô hình của bạn thường trông như thế này:

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

Nhưng nếu bạn xem tài liệu, bạn sẽ thấy rằng họ có thể làm được nhiều hơn thế. Bạn có thể chuyển các tham số khác cho các phương thức đó và thay đổi cách chúng hoạt động.

scope là một trong những điều hữu ích nhất. Nó hoạt động giống như scope từ trước đó:

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

Bây giờ, bạn có thể tải trước liên kết mới của mình với 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."]

Thay vì 6 lệnh gọi SQL, chúng tôi chỉ thực hiện hai lệnh.

(Sử dụng class_name , bạn có thể có nhiều liên kết với cùng một đối tượng. Điều này thường có ích.)

Sao chép thì sao?

Vẫn có thể có một vấn đề ở đây. where("rating > 3.0") bây giờ là trên lớp Nhà hàng của bạn. Nếu sau đó bạn đã thay đổi đánh giá tích cực thành rating > 3.5 , bạn phải cập nhật nó hai lần!

Nó trở nên tồi tệ hơn:Nếu bạn cũng muốn nhận được tất cả các đánh giá tích cực mà một người đã từng để lại, bạn cũng phải sao chép phạm vi đó trên lớp Người dùng:

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

Nó không KHÔ lắm.

Tuy nhiên, có một cách dễ dàng để giải quyết vấn đề này. Bên trong của where , bạn có thể sử dụng positive phạm vi bạn đã thêm vào lớp Đánh giá:

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

Bằng cách đó, ý tưởng những gì khiến một bài đánh giá trở thành bài đánh giá tích cực vẫn chỉ ở một nơi.

Phạm vi là rất tốt. Ở đúng nơi, họ có thể làm cho việc truy vấn dữ liệu của bạn trở nên dễ dàng và thú vị. Nhưng nếu bạn muốn tránh N + 1 truy vấn, bạn phải cẩn thận với chúng.

Vì vậy, nếu một phạm vi bắt đầu khiến bạn gặp rắc rối, hãy gói nó trong một liên kết và tải trước nó . Nó không còn nhiều công việc nữa và nó sẽ giúp bạn tiết kiệm một loạt các lệnh gọi SQL.