क्लाइंट वहां कैसे पहुंचा?
विवरण में गोता लगाने से पहले, आइए यह समझने की कोशिश करें कि इस स्थिति में कोई ऐप कैसे समाप्त हो सकता है। हम एक साधारण users . से शुरू करते हैं मेज़। कुछ हफ्तों के बाद, हमें अंतिम साइन इन समय निर्धारित करने में सक्षम होना चाहिए ताकि हम users.last_sign_in_at जोड़ सकें। . फिर हमें यूजर का नाम जानना होगा। हम first_name add जोड़ते हैं और last_name . ट्विटर हैंडल? एक और कॉलम। गिटहब प्रोफाइल? फ़ोन नंबर? कुछ महीनों के बाद तालिका दिमागी दबदबा बन जाती है।
इसमें क्या गलत है?
एक बड़ी तालिका कई समस्याओं को इंगित करती है:
Userकई असंबंधित जिम्मेदारियां हैं। इससे इसे समझना, बदलना और परीक्षण करना अधिक कठिन हो जाता है।- ऐप और डेटाबेस के बीच डेटा का आदान-प्रदान करने के लिए अतिरिक्त बैंडविड्थ की आवश्यकता होती है।
- भारी मॉडल को स्टोर करने के लिए ऐप को अधिक मेमोरी की आवश्यकता होती है।
ऐप ने User प्राप्त किया प्रमाणीकरण और प्राधिकरण उद्देश्यों के लिए प्रत्येक अनुरोध पर लेकिन आमतौर पर केवल कुछ ही कॉलम का उपयोग किया जाता है। समस्या को ठीक करने से डिज़ाइन और प्रदर्शन दोनों में सुधार होगा।
तालिका निकालना
हम शायद ही कभी इस्तेमाल किए गए कॉलम को नई टेबल (या टेबल) में एक्सट्रेक्ट करके समस्या का समाधान कर सकते हैं . उदाहरण के लिए, हम प्रोफ़ाइल जानकारी निकाल सकते हैं (first_name , आदि) profiles . में निम्नलिखित चरणों के साथ:
profilesusers. में प्रोफ़ाइल से संबंधित स्तंभों को डुप्लिकेट करने वाले स्तंभों के साथ ।profile_idजोड़ेंusers. के लिए . इसेNULL. पर सेट करें अभी के लिए।usersमें प्रत्येक पंक्ति के लिए ,profiles. में एक पंक्ति डालें जो प्रोफ़ाइल-संबंधित कॉलम को डुप्लिकेट करता है।- बिंदु
profile_idusers. में संबंधित पंक्ति का 3 में डाली गई पंक्ति में। - नहीं नहीं
users.profile_idmake बनाएं गैर-NULL. ऐप को अभी तक अपने अस्तित्व के बारे में पता नहीं है इसलिए यह टूट जाएगा।
हमें संदर्भों को users.first_name . से बदलना होगा profiles.first_name . के साथ और इसी तरह। यदि हम मुट्ठी भर संदर्भों के साथ केवल कुछ कॉलम निकाल रहे हैं तो मेरा सुझाव है कि हम इसे मैन्युअल रूप से करें। लेकिन जैसे ही हम सोचते हैं "ओह, नहीं। यह अब तक का सबसे खराब काम है!" हमें एक विकल्प की तलाश करनी चाहिए।
समस्या की उपेक्षा न करें। कोड का एक हिस्सा जिससे हर कोई बचता है वह और भी खराब हो जाएगा और अधिक असावधानी से पीड़ित होगा . दुष्चक्र को तोड़ने का सबसे आसान तरीका है छोटी शुरुआत करना।
आगे पढ़ें, यदि आप उत्सुक हैं कि मेरे मुवक्किल ने समस्या का समाधान कैसे किया।
कोड को एक बार में एक लाइन ठीक करना
सबसे वृद्धिशील दृष्टिकोण एक समय में पुराने कॉलम के एक संदर्भ को ठीक कर रहा है। आइए first_name को स्थानांतरित करने पर ध्यान दें users . से profiles . के लिए ।
सबसे पहले, Profile create बनाएं साथ:
rails generate model Profile first_name:string
फिर users . से एक संदर्भ जोड़ें profiles . के लिए और कॉपी करें users.first_name profiles . के लिए :
class ExtractUsersFirstNameToProfiles < ActiveRecord::Migration
# Redefine the models to break dependency on production code. We need
# vanilla models without callbacks, etc. Also, removing a model in the future
# might break the migration.
class User < ActiveRecord::Base; end
class Profile < ActiveRecord::Base; end
def up
add_reference :users, :profile, index: true, unique: true, foreign_key: true
User.find_each do |user|
profile = Profile.create!(first_name: user.first_name)
user.update!(profile_id: profile.id)
end
change_column_null :users, :profile_id, false
end
def down
remove_reference :users, :profile
end
end
क्योंकि यह प्रत्येक उपयोगकर्ता को ठीक एक प्रोफ़ाइल के लिए बाध्य करता है, users . से एक संदर्भ profiles . के लिए विपरीत संदर्भ के लिए बेहतर है।
डेटाबेस संरचना के साथ, हम first_name . को प्रत्यायोजित कर सकते हैं User . से Profile . पर . मेरे मुवक्किल की कई आवश्यकताएं थीं:
- एक्सेसर्स को संबंधित
profilesका उपयोग करना चाहिए . उन्हें यह भी लॉग इन करना चाहिए कि पदावनत एक्सेसर को कहाँ से बुलाया गया था। Userसहेजा जा रहा हैProfileautomatically को अपने आप सहेजना चाहिए बहिष्कृत एक्सेसर्स का उपयोग करके कोड को तोड़ने से बचने के लिए।User#first_name_changed?और अन्यActiveModel::Dirtyविधियों को अभी भी काम करना चाहिए।
इसका अर्थ है User इस तरह दिखना चाहिए:
class User < ActiveRecord::Base
# We need autosave as the client code might be unaware of
# Profile#first_name and still reference User#first_name.
belongs_to :profile, autosave: true
def first_name
log_backtrace(:first_name)
profile.first_name
end
def first_name=(new_first_name)
log_backtrace(:first_name)
# Call super so that User#first_name_changed? and similar still work as
# expected.
super
profile.first_name = new_first_name
end
private
def log_backtrace(name)
filtered_backtrace = caller.select do |item|
item.start_with?(Rails.root.to_s)
end
Rails.logger.warn(<<-END)
A reference to an obsolete attribute #{name} at:
#{filtered_backtrace.join("\n")}
END
end
end
इन परिवर्तनों के बाद, ऐप वही काम करता है लेकिन Profile के अतिरिक्त संदर्भों के कारण थोड़ा धीमा हो सकता है (यदि प्रदर्शन एक मुद्दा बन जाता है तो बस ऐपसिग्नल जैसे टूल का उपयोग करें)। कोड विरासती विशेषताओं के सभी संदर्भों को लॉग करता है, यहां तक कि अप्राप्य भी (उदा. user[attr] = ... या user.send("#{attr}=", ...) ) इसलिए हम grep . होने पर भी उन सभी का पता लगा पाएंगे अनुपयोगी है।
इस बुनियादी ढांचे के साथ, हम users.first_name . के एक संदर्भ को ठीक करने के लिए प्रतिबद्ध हो सकते हैं नियमित समय पर, उदा। हर सुबह (जल्दी जीत के साथ दिन की शुरुआत करने के लिए) या दोपहर के आसपास (एक केंद्रित सुबह के बाद कुछ आसान काम करने के लिए)। यह प्रतिबद्धता आवश्यक है क्योंकि हमारा लक्ष्य समस्या को ठीक करने के लिए मानसिक बाधाओं को कम करना है . ऊपर दिए गए कोड को बिना कार्रवाई किए छोड़ देने से ऐप और भी खराब हो जाएगा।
सभी पदावनत संदर्भों को हटाने के बाद (और grep . के साथ पुष्टि करते हुए) और लॉग) हम अंत में users.first_name . को छोड़ सकते हैं :
class RemoveUsersFirstName < ActiveRecord::Migration
def change
remove_column :users, :first_name, :string
end
end
हमें User . में जोड़े गए कोड से भी छुटकारा पाना चाहिए क्योंकि अब इसकी आवश्यकता नहीं है।
सीमाएं
यह विधि आपके मामले पर लागू हो सकती है लेकिन इसकी कुछ सीमाओं को ध्यान में रखें:
- यह
User.update_allजैसी बल्क क्वेरी को हैंडल नहीं करता है । - यह कच्ची SQL क्वेरी को हैंडल नहीं करता है।
- यह बंदर-पैच तोड़ सकता है (याद रखें कि निर्भरताएं उन्हें भी पेश कर सकती हैं)।
Userऔरprofilesअगरprofiles.first_name. तो सिंक से बाहर हो सकता है अपडेट किया गया है लेकिनusers.first_nameनहीं है।
आप उनमें से कुछ पर काबू पाने में सक्षम हो सकते हैं। उदाहरण के लिए, आप मॉडल को सर्विस ऑब्जेक्ट के साथ सिंक में रख सकते हैं या profiles . पर कॉलबैक कर सकते हैं . या यदि आप PostgreSQL का उपयोग करते हैं तो आप अंतरिम में एक भौतिक दृश्य का उपयोग करने पर विचार कर सकते हैं।
बस!
लेख का सबसे महत्वपूर्ण सबक यह है कि उस कोड से बचें, जिससे बदबू आती है, लेकिन इसके बजाय इससे निपटें . यदि कार्य भारी है तो नियमित समय पर पुनरावृत्त रूप से कार्य करें। लेख प्रस्तुत किया गया a तालिका निकालने पर विचार करने की विधि कठिन है। यदि आप इसे लागू नहीं कर सकते हैं तो कुछ और देखें। अगर आपको पता नहीं है कि कैसे तो बस मुझे एक लाइन छोड़ दो। मैं मदद करने की कोशिश करूंगा। अपने बिट्स को सड़ने न दें।