यह लेख कोरियाई में भी उपलब्ध है, सूनसांग होंग के लिए धन्यवाद!
रेल का दायरा आपके इच्छित रिकॉर्ड ढूंढना आसान बनाता है:
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 क्वेरी है। धीमी रेल ऐप्स का सबसे बड़ा कारण।
आप इसे बहुत आसानी से ठीक कर सकते हैं, हालांकि, यदि आप संबंध के बारे में अलग तरीके से सोचते हैं।
स्कोप को एसोसिएशन में बदलें
जब आप रेल एसोसिएशन विधियों का उपयोग करते हैं, जैसे 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."]
6 SQL कॉल के बजाय, हमने केवल दो कॉल किए।
(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 कॉल का एक गुच्छा बचाएगा।