10

प्रीलोडिंग को अनदेखा करती है क्या रेलवे में has_many एसोसिएशन के लिए एक विधि का प्रतिनिधि बनाना संभव है, और अभी भी उस एसोसिएशन पर प्रीलोड किए गए डेटा को डिलीटर के नियमों का पालन करते समय सहेजना संभव है? वर्तमान में मुझे ऐसा लगता है कि आपको एक या दूसरे को चुनने के लिए मजबूर होना पड़ता है। यही है: अपने प्रीलोड किए गए डेटा को प्रतिनिधि न दें, या अपना प्रीलोड किए गए डेटा और प्रतिनिधि को खो दें।has_many एसोसिएशन के लिए प्रतिनिधि विधि

उदाहरण: मैं निम्नलिखित दो मॉडल हैं:

class User < ApplicationRecord 
    has_many :blogs 

    delegate :all_have_title?, to: :blogs, prefix: false, allow_nil: false 

    def all_blogs_have_title? 
    blogs.all? {|blog| blog.title.present?} 
    end 
end 


class Blog < ApplicationRecord 
    belongs_to :user 

    def self.all_have_title? 
    all.all? {|blog| blog.title.present?} 
    end 
end 

नोटिस: कि User#all_blogs_have_title?all_have_title? के प्रतिनिधिमंडल पद्धति के रूप में सटीक एक ही बात करता है।

निम्नलिखित, जैसा कि मैं इसे समझता हूं, डेमेटर के कानून का उल्लंघन करता है। हालांकि:

user = User.includes(:blogs).first 
    User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] 
    Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = 1 
    => #<User id: 1, name: "all yes", created_at: "2017-12-05 20:28:00", updated_at: "2017-12-05 20:28:00"> 

user.all_blogs_have_title? 
=> true 

सूचना: यह आपके प्रीलोडेड डेटा का कहना है जब मैं user.all_blogs_have_title? कहा जाता है यह एक अतिरिक्त क्वेरी नहीं किया। हालांकि, ध्यान दें कि विधि all_blogs_have_title?Blog विशेषताएँ पूछ रही है, जो डेमेटर के कानून का उल्लंघन कर रही है।

अन्य तरीका Demeter के कानून लागू होता है, लेकिन आप पहले से लोड डेटा खो जो:

user = User.includes(:blogs).first 
    User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] 
    Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = 1 
    => #<User id: 1, name: "all yes", created_at: "2017-12-05 20:28:00", updated_at: "2017-12-05 20:28:00"> 

user.all_have_title? 
    Blog Load (0.2ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = ? [["user_id", 1]] 
    => true 

उम्मीद है कि दोनों कार्यान्वयन के नकारात्मक पक्ष यह स्पष्ट है। आदर्श रूप से: मैं इसे प्रतिनिधि कार्यान्वयन के साथ दूसरा तरीका करना चाहता हूं, लेकिन उस प्रीलोड किए गए डेटा को बनाए रखने के लिए। क्या यह संभव है?

उत्तर

4

स्पष्टीकरण

कारण है कि all_have_title? प्रतिनिधिमंडल अपने उदाहरण में ठीक से काम नहीं करता है कि आपके blogs संघ के लिए विधि सौंपने कर रहे हैं, लेकिन अभी तक एक Blog वर्ग विधि है, जो विभिन्न इकाइयों के रूप में यह परिभाषित करने और इस प्रकार रिसीवर।

इस बिंदु पर निम्नलिखित सभी लोग एक प्रश्न पूछेंगे क्यों अपवाद उठाया गया है जब ओपी द्वारा प्रदान किए गए दूसरे उदाहरण में user.all_have_title? पर कॉल किया गया है। इस के पीछे कारण हमारे उदाहरण namings के कारण ActiveRecord::Associations::CollectionProxy दस्तावेज में सविस्तार है, जो अलग ढंग से व्यक्त (जो user.blogs कॉल के परिणामस्वरूप वस्तु वर्ग है) में कहा गया:

कि user.blogs में संघ प्रॉक्सी के रूप में user में वस्तु है @owner, blogs का संग्रह @target के रूप में, और @reflection ऑब्जेक्ट :has_many मैक्रो का प्रतिनिधित्व करता है।
यह कक्षा अज्ञात विधियों को @target पर method_missing के माध्यम से प्रस्तुत करती है।

तो चीजें हैं जो हो रहा है का क्रम इस प्रकार है:

  1. delegate आरंभीकरण पर User मॉडल में has_many दायरे में परिभाषित करता है all_have_title? उदाहरण विधि;
  2. जब userall_have_title? पर कॉल किया गया तो has_many एसोसिएशन को विधि प्रदान की जाती है;
  3. क्योंकि वहां ऐसी कोई विधि परिभाषित नहीं है, इसे Blog वर्ग all_have_title? विधि method_missing के माध्यम से विधिबद्ध किया गया है;
  4. all विधि current_scope जो (इस बिंदु पर scoped_attributes{"user_id"=>1} मूल्य रखा है) user_id हालत रखती है साथ Blog पर कहा जाता है, तो पहले से लोड होने के बारे में कोई जानकारी नहीं है, क्योंकि मूल रूप से क्या हो रहा है यह है:

    Blog.where(user_id: 1) 
    
    के लिए

    प्रत्येक user अलग से, जो प्रीलोडिंग की तुलना में महत्वपूर्ण अंतर है जो पहले किया गया था, in का उपयोग करके कई मानों द्वारा रिकॉर्ड से संबंधित प्रश्नों से संबंधित प्रश्न, लेकिन यहां किए गए एक व्यक्ति को = के साथ एक रिकॉर्ड का सवाल है (यही कारण है कि क्वेरी इन दोनों कॉलों के बीच भी कैश नहीं है)।

समाधान

दोनों के लिए स्पष्ट रूप से विधि संपुटित और के रूप में चिह्नित एक रिश्ता-आधारित (User और Blog के बीच) आप को परिभाषित करने और यह तर्क है वर्णन has_many संघ दायरे में होना चाहिए:

class User 
    delegate :all_have_title?, to: :blogs, prefix: false, allow_nil: false 

    has_many :blogs do 
    def all_have_title? 
     all? { |blog| blog.title.present? } 
    end 
    end 
end 

इस प्रकार आपके द्वारा किए जाने वाले कॉलिंग के परिणामस्वरूप निम्नलिखित 2 प्रश्नों का परिणाम होना चाहिए:

user = User.includes(:blogs).first 
=> #<User:0x00007f9ace1067e0 
    User Load (0.8ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1 
    Blog Load (1.4ms) SELECT `blogs`.* FROM `blogs` WHERE `blogs`.`user_id` IN (1) 
user.all_have_title? 
=> true 

इस तरह User स्पष्ट रूप से Blog के गुणों के साथ काम नहीं करता है और आप प्रीलोड किए गए डेटा को खो नहीं देते हैं।आप संघ तरीकों सीधे title विशेषता (all विधि में ब्लॉक) के साथ काम कर नहीं करना चाहते हैं, तो आप Blog मॉडल में एक उदाहरण विधि को परिभाषित करने और वहाँ सभी तर्क को परिभाषित कर सकते हैं:

class Blog 
    def has_title? 
    title.present? 
    end 
end 
3

यहां एक समाधान है जो डेमेटर के कानून का पालन करता है और प्रीलोड किए गए डेटा का सम्मान करता है (डेटाबेस को फिर से नहीं मारा जाता है)। यह निश्चित रूप से एक सा अजीब है, लेकिन मैं किसी भी अन्य समाधान नहीं मिल सकता है, और मैं वास्तव में पता है कि अन्य लोगों को इस बारे में सोचना चाहता हूँ:

मॉडल

class User < ApplicationRecord  
    has_many :blogs 

    def all_blogs_have_title? 
    blogs.all_have_title_present?(self) 
    end 
end 

class Blog < ApplicationRecord 
    belongs_to :user 

    def self.all_have_title_present?(user) 
    user.blogs.any? && user.blogs.all? {|blog| blog.title.present?} 
    end 
end 

प्रयोग

user = User.includes(:blogs).first 
    User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] 
    Blog Load (0.1ms) SELECT "blogs".* FROM "blogs" WHERE "blogs"."user_id" = 1 
    => #<User id: 1, name: "all yes", created_at: "2017-12-05 20:28:00", updated_at: "2017-12-05 20:28:00"> 

user.all_blogs_have_title? 
=> true 

तो हम देखते हैं कि यह डेटाबेस को फिर से मार रहा है (प्रीलोड किए गए डेटा का सम्मान नहीं कर रहा है), और इसके बजाय user इसके पड़ोसी के गुणों तक पहुंचने के बजाय (Blog), यह अपने पड़ोसी को प्रश्न प्रस्तुत करता है और के सवालों के जवाब देने के लिए पड़ोसी (फिर से: Blog) को अपने गुण के उत्तर देने के लिए अनुमति देता है।

अजीब बात Blog मॉडल जहां वर्ग विधि पूछता user.blogs अंदर स्पष्ट रूप से है, इसलिए BlogUser पर संघ के बारे में जानता है। लेकिन शायद यह ठीक है क्योंकि, Blog और User एक-दूसरे के साथ एक सहयोग साझा करते हैं।

0

यह scope_delegation gem अपने काम करेंगे ।

अगर आप इस

class User < ApplicationRecord 
    has_many :blogs 

    delegate :all_have_title?, to: :blogs, prefix: false, allow_nil: false 
end 

class Blog < ApplicationRecord 
    belongs_to :user 

    def self.all_have_title? 
    where.not(title: nil) 
    end 
end 

की तरह अपने काम परिभाषित करेगा यह काम करना चाहिए :)

संबंधित मुद्दे