Ruby on Rails
सर्वश्रेष्ठ अभ्यास रेल
खोज…
अपने आप को मत दोहराओ (DRY)
स्वच्छ कोड बनाए रखने में मदद करने के लिए, रेल DRY के सिद्धांत का अनुसरण करता है।
इसमें जब भी संभव हो, कई स्थानों पर समान कोड की नकल करने के बजाय जितना संभव हो उतना दोबारा कोड का उपयोग करना (उदाहरण के लिए, भाग का उपयोग करके)। यह त्रुटियों को कम करता है, आपके कोड को साफ रखता है और एक बार कोड लिखने के सिद्धांत को लागू करता है और फिर इसका उपयोग करता है। एक ही कोड के कई हिस्सों को अपडेट करने की तुलना में एक जगह कोड अपडेट करना आसान और अधिक कुशल है। इस प्रकार आपके कोड को अधिक मॉड्यूलर और मजबूत बनाते हैं।
इसके अलावा वसा मॉडल, स्कीनी नियंत्रक DRY है, क्योंकि आप अपने मॉडल में कोड लिखते हैं और नियंत्रक में केवल कॉल करते हैं, जैसे:
# Post model
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) }
# Any controller
def index
....
@unpublished_posts = Post.unpublished
....
end
def others
...
@unpublished_posts = Post.unpublished
...
end
यह एक एपीआई संचालित संरचना का नेतृत्व करने में भी मदद करता है जहां आंतरिक तरीके छिपे हुए हैं और एपीआई फैशन में गुजरने वाले मापदंडों के माध्यम से परिवर्तन प्राप्त किए जाते हैं।
कॉन्फिगरेशन पर सम्मलेन
रेल में, आप अपने डेटाबेस के लिए नियंत्रकों, विचारों और मॉडलों को देख सकते हैं।
भारी कॉन्फ़िगरेशन की आवश्यकता को कम करने के लिए, रेल आवेदन के साथ काम करने में आसानी के लिए नियम लागू करता है। आप अपने स्वयं के नियमों को परिभाषित कर सकते हैं लेकिन शुरुआत के लिए (और बाद के लिए) यह एक अच्छा विचार है कि रूल प्रदान करने वाले सम्मेलनों से चिपके रहें।
ये सम्मेलन विकास को गति देंगे, अपने कोड को संक्षिप्त और पठनीय रखेंगे, और आपको अपने आवेदन के अंदर एक आसान नेविगेशन की अनुमति देंगे।
परंपराएं शुरुआती लोगों के लिए प्रवेश के लिए बाधाओं को कम करती हैं। रेल में बहुत सारे सम्मेलन होते हैं, जिनके बारे में किसी शुरुआतकर्ता को भी जानने की आवश्यकता नहीं होती है, लेकिन अज्ञानता में इसका लाभ उठा सकते हैं। यह जानने के बिना महान एप्लिकेशन बनाना संभव है कि सब कुछ ऐसा क्यों है।
उदाहरण के लिए
यदि आपके पास एक डेटाबेस टेबल है जिसे प्राथमिक कुंजी id
साथ orders
कहा जाता है, तो मिलान मॉडल को order
कहा जाता है और नियंत्रक जो सभी तर्क को संभालता है, उसे orders_controller
नाम दिया orders_controller
। दृश्य अलग-अलग क्रियाओं में विभाजित है: यदि नियंत्रक में एक new
और edit
कार्य है, तो एक new
और edit
दृश्य भी है।
उदाहरण के लिए
एक ऐप बनाने के लिए आप बस rails new app_name
चलाते हैं। यह लगभग 70 फ़ाइलों और फ़ोल्डरों को उत्पन्न करेगा जिसमें आपके रेल एप्लिकेशन के लिए बुनियादी ढांचे और नींव शामिल हैं।
उसमे समाविष्ट हैं:
- आपके मॉडल (डेटाबेस लेयर), कंट्रोलर्स और व्यूज़ को होल्ड करने के लिए फोल्डर्स
- आपके आवेदन के लिए यूनिट परीक्षण करने के लिए फ़ोल्डर
- जावास्क्रिप्ट और सीएसएस फ़ाइलों की तरह अपनी वेब संपत्ति रखने के लिए फ़ोल्डर
- HTTP 400 प्रतिक्रियाओं के लिए डिफ़ॉल्ट फाइलें (यानी फ़ाइल नहीं मिली)
- कई अन्य
फैट मॉडल, स्कीनी नियंत्रक
"फैट मॉडल, स्कीनी कंट्रोलर" संदर्भित करता है कि एमवीसी के एम और सी हिस्से आदर्श रूप से एक साथ कैसे काम करते हैं। अर्थात्, किसी भी गैर-प्रतिक्रिया-संबंधी तर्क को मॉडल में जाना चाहिए, आदर्श रूप से एक अच्छा, परीक्षण योग्य विधि में। इस बीच, "पतला" नियंत्रक केवल दृश्य और मॉडल के बीच एक अच्छा इंटरफ़ेस है।
व्यवहार में, इसके लिए विभिन्न प्रकार के रिफैक्टरिंग की आवश्यकता हो सकती है, लेकिन यह सभी एक विचार से नीचे आता है: किसी भी तर्क को स्थानांतरित करने से जो मॉडल (नियंत्रक के बजाय) की प्रतिक्रिया के बारे में नहीं है, न केवल आपने पुन: उपयोग को बढ़ावा दिया है जहां संभव हो, लेकिन आपने अनुरोध के संदर्भ के बाहर अपने कोड का परीक्षण करना भी संभव बना दिया है।
आइए एक साधारण उदाहरण देखें। कहो कि आपके पास इस तरह का कोड है:
def index
@published_posts = Post.where('published_at <= ?', Time.now)
@unpublished_posts = Post.where('published_at IS NULL OR published_at > ?', Time.now)
end
आप इसे इसमें बदल सकते हैं:
def index
@published_posts = Post.published
@unpublished_posts = Post.unpublished
end
फिर, आप तर्क को अपने पोस्ट मॉडल में स्थानांतरित कर सकते हैं, जहां यह इस तरह दिख सकता है:
scope :published, ->(timestamp = Time.now) { where('published_at <= ?', timestamp) }
scope :unpublished, ->(timestamp = Time.now) { where('published_at IS NULL OR published_at > ?', timestamp) }
Default_scope से सावधान रहें
ActiveRecord में डिफ़ॉल्ट रूप से मॉडल को स्वचालित रूप से स्कोप करने के लिए default_scope
शामिल है।
class Post
default_scope ->{ where(published: true).order(created_at: :desc) }
end
उपरोक्त कोड उन पोस्टों की सेवा करेगा जो पहले से ही प्रकाशित हैं जब आप मॉडल पर कोई क्वेरी करते हैं।
Post.all # will only list published posts
यह गुंजाइश है, जबकि सहज दिखने वाला, कई छिपे हुए साइड-इफेक्ट हैं जो आप नहीं चाहते हैं।
default_scope
और order
चूंकि आपने default_scope
में एक order
घोषित किया था, इसलिए Post
पर कॉलिंग order
को डिफ़ॉल्ट को ओवरराइड करने के बजाय अतिरिक्त ऑर्डर के रूप में जोड़ा जाएगा।
Post.order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."created_at" DESC, "posts"."updated_at" DESC
यह संभवतः वह व्यवहार नहीं है जिसे आप चाहते थे; आप इस order
को पहले स्कोप से बाहर कर सकते हैं
Post.except(:order).order(updated_at: :desc)
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' ORDER BY "posts"."updated_at" DESC
default_scope
और मॉडल इनिशियलाइज़ेशन
किसी भी अन्य ActiveRecord::Relation
साथ के रूप में, default_scope
इसे से आरंभ किए गए मॉडल की डिफ़ॉल्ट स्थिति को बदल देगा।
उपरोक्त उदाहरण में, Post
में डिफ़ॉल्ट रूप से सेट where(published: true)
, और इसलिए Post
से नए मॉडल भी सेट होंगे।
Post.new # => <Post published: true>
unscoped
default_scope
को पहले unscoped
कहकर नामांकित किया जा सकता है, लेकिन इसके दुष्प्रभाव भी हैं। उदाहरण के लिए, एक एसटीआई मॉडल लें:
class Post < Document
default_scope ->{ where(published: true).order(created_at: :desc) }
end
डिफ़ॉल्ट रूप से, Post
विरुद्ध क्वेरीज़ को 'Post'
वाले कॉलम type
करने के लिए बंद कर दिया जाएगा। लेकिन unscoped
आपके स्वयं के default_scope
के साथ-साथ इसे भी साफ़ कर देगा, इसलिए यदि आप unscoped
उपयोग करते हैं तो आपको इसके लिए भी ध्यान रखना होगा।
Post.unscoped.where(type: 'Post').order(updated_at: :desc)
unscoped
और मॉडल एसोसिएशन
Post
और User
बीच संबंध पर विचार करें
class Post < ApplicationRecord
belongs_to :user
default_scope ->{ where(published: true).order(created_at: :desc) }
end
class User < ApplicationRecord
has_many :posts
end
एक व्यक्तिगत User
प्राप्त करके, आप इससे संबंधित पोस्ट देख सकते हैं:
user = User.find(1)
user.posts
SELECT "posts".* FROM "posts" WHERE "posts"."published" = 't' AND "posts"."user_id" = ? ORDER BY "posts"."created_at" DESC [["user_id", 1]]
लेकिन आप posts
संबंध से default_scope
को खाली करना चाहते हैं, इसलिए आप unscoped
उपयोग करते हैं
user.posts.unscoped
SELECT "posts".* FROM "posts"
यह user_id
कंडीशन के साथ-साथ default_scope
को भी मिटा देता है।
के लिए एक उदाहरण यूज-केस default_scope
उस सब के बावजूद, ऐसी स्थितियां हैं जहां default_scope
का उपयोग करना उचित है।
एक बहु-किरायेदार प्रणाली पर विचार करें जहां एक ही एप्लिकेशन से कई उप-डोमेन परोसे जाते हैं लेकिन अलग-अलग डेटा के साथ। इस अलगाव को प्राप्त करने का एक तरीका default_scope
। अन्य मामलों में डाउनसाइड्स यहां अपडाउन हो जाता है।
class ApplicationRecord < ActiveRecord::Base
def self.inherited(subclass)
super
return unless subclass.superclass == self
return unless subclass.column_names.include? 'tenant_id'
subclass.class_eval do
default_scope ->{ where(tenant_id: Tenant.current_id) }
end
end
end
आपको केवल अनुरोध में कुछ जल्दी Tenant.current_id
सेट करने की आवश्यकता है, और tenant_id
किसी भी तालिका में बिना किसी अतिरिक्त कोड के स्वतः ही स्कोप हो जाएगा। तात्कालिक रिकॉर्ड्स स्वचालित रूप से किरायेदार आईडी को विरासत में प्राप्त करेंगे जो उनके तहत बनाई गई थी।
इस उपयोग-मामले के बारे में महत्वपूर्ण बात यह है कि अनुरोध के अनुसार एक बार गुंजाइश निर्धारित की जाती है, और यह परिवर्तित नहीं होती है। केवल उन्हीं मामलों की आपको आवश्यकता होगी unscoped
यहां विशेष मामले हैं जैसे कि पृष्ठभूमि कार्यकर्ता ऐसे हैं जो अनुरोध के दायरे से बाहर चलते हैं।
आपको इसकी आवश्यकता नहीं है (YAGNI)
यदि आप एक विशेषता के बारे में "YAGNI" (आप इसकी आवश्यकता नहीं है) कह सकते हैं, तो आप इसे लागू नहीं करना बेहतर समझते हैं। सादगी पर ध्यान केंद्रित करने के माध्यम से विकास के बहुत से समय को बचाया जा सकता है। इस तरह की सुविधाओं को लागू करने से समस्याएं हो सकती हैं:
समस्या
Overengineering
यदि किसी उत्पाद की तुलना में यह अधिक जटिल है, तो यह इंजीनियर से अधिक है। आमतौर पर इन "अभी तक उपयोग नहीं किए गए" सुविधाओं का उपयोग कभी नहीं किया जाएगा जिस तरह से वे लिखे गए थे और यदि उन्हें कभी भी उपयोग किया जाता है तो उन्हें फिर से भरना होगा। समयपूर्व अनुकूलन, विशेष रूप से प्रदर्शन अनुकूलन, अक्सर डिजाइन निर्णय लेते हैं जो भविष्य में गलत साबित होंगे।
कोड ब्लोट
कोड ब्लोट का अर्थ है अनावश्यक जटिल कोड। यह उदाहरण के लिए अमूर्तता, अतिरेक या डिज़ाइन पैटर्न के गलत अनुप्रयोग द्वारा हो सकता है। कोड बेस को समझना, भ्रमित करना और बनाए रखना महंगा हो जाता है।
अनवरत वृद्धि # अनियंत्रित विस्तार
फ़ीचर क्रीप में नई विशेषताओं को जोड़ने का उल्लेख है जो उत्पाद की मुख्य कार्यक्षमता से परे हैं और उत्पाद की अनावश्यक रूप से उच्च जटिलता की ओर ले जाते हैं।
लंबे विकास का समय
जो समय आवश्यक सुविधाओं को विकसित करने के लिए इस्तेमाल किया जा सकता है, वह अनावश्यक सुविधाओं को विकसित करने के लिए खर्च किया जाता है। उत्पाद देने में अधिक समय लगता है।
समाधान
KISS - यह सरल रखें, बेवकूफ
KISS के अनुसार, अधिकांश सिस्टम सबसे अच्छा अगर वे सरल डिजाइन किए हैं काम करते हैं। जटिलता को कम करने के लिए सादगी एक प्राथमिक डिजाइन लक्ष्य होना चाहिए। यह उदाहरण के लिए "एकल जिम्मेदारी सिद्धांत" का पालन करके प्राप्त किया जा सकता है।
YAGNI - आपको इसकी आवश्यकता नहीं होगी
थोड़ा ही काफी है। हर सुविधा के बारे में सोचो, क्या इसकी वास्तव में जरूरत है? यदि आप किसी भी तरह से सोच सकते हैं कि यह YAGNI है, तो इसे छोड़ दें। जरूरत पड़ने पर इसे विकसित करना बेहतर है।
निरंतर रिफलेक्टरिंग
उत्पाद में लगातार सुधार हो रहा है। रिफैक्टरिंग के साथ, हम यह सुनिश्चित कर सकते हैं कि उत्पाद सबसे अच्छा अभ्यास के अनुसार किया जा रहा है और एक पैच कार्य को पतित नहीं करता है।
डोमेन ऑब्जेक्ट्स (कोई और अधिक मोटी मॉडल)
"फैट मॉडल, स्किनी कंट्रोलर" एक बहुत अच्छा पहला कदम है, लेकिन एक बार जब आपका कोडबेस बढ़ना शुरू हो जाता है तो यह अच्छी तरह से पैमाने पर नहीं होता है।
आइए मॉडलों की एकल जिम्मेदारी पर विचार करें। मॉडल की एकल जिम्मेदारी क्या है? क्या यह व्यावसायिक तर्क है? क्या यह गैर-प्रतिक्रिया-संबंधी तर्क रखने के लिए है?
नहीं, इसकी जिम्मेदारी दृढ़ता परत और इसके अमूर्त को संभालना है।
व्यावसायिक तर्क, साथ ही किसी भी गैर-प्रतिक्रिया-संबंधी तर्क और गैर-दृढ़ता से संबंधित तर्क, डोमेन ऑब्जेक्ट्स में जाने चाहिए।
डोमेन ऑब्जेक्ट ऐसी कक्षाएं हैं जिन्हें समस्या के क्षेत्र में केवल एक जिम्मेदारी के लिए डिज़ाइन किया गया है। अपनी कक्षाओं को उनके द्वारा हल की जाने वाली समस्याओं के लिए " चिल्लाओ उनका आर्किटेक्चर " दें।
व्यवहार में, आपको स्कीनी मॉडल, स्कीनी विचार और स्कीनी नियंत्रकों की ओर प्रयास करना चाहिए। आपके समाधान का आर्किटेक्चर आपके द्वारा चुने गए ढांचे से प्रभावित नहीं होना चाहिए।
उदाहरण के लिए
मान लीजिए कि आप एक बाज़ार हैं, जो स्ट्राइप के माध्यम से आपके ग्राहकों को एक निश्चित 15% कमीशन देता है। यदि आप एक निश्चित 15% कमीशन चार्ज करते हैं, तो इसका मतलब है कि आपका कमीशन ऑर्डर की राशि के आधार पर बदलता है क्योंकि स्ट्राइप 2.9% + 30 2.9 चार्ज करता है।
आपके द्वारा कमीशन के रूप में ली जाने वाली amount*0.15 - (amount*0.029 + 0.30)
: amount*0.15 - (amount*0.029 + 0.30)
होनी चाहिए।
इस तर्क को मॉडल में न लिखें:
# app/models/order.rb
class Order < ActiveRecord::Base
SERVICE_COMMISSION = 0.15
STRIPE_PERCENTAGE_COMMISSION = 0.029
STRIPE_FIXED_COMMISSION = 0.30
...
def commission
amount*SERVICE_COMMISSION - stripe_commission
end
private
def stripe_commission
amount*STRIPE_PERCENTAGE_COMMISSION + STRIPE_FIXED_COMMISSION
end
end
जैसे ही आप एक नई भुगतान विधि के साथ एकीकृत होते हैं, आप इस मॉडल के अंदर इस कार्यक्षमता को स्केल नहीं कर पाएंगे।
इसके अलावा, जैसे ही आप अधिक व्यावसायिक तर्क को एकीकृत करना शुरू करते हैं, आपका Order
ऑब्जेक्ट सामंजस्य खोना शुरू कर देगा।
आदेश जारी रखने की जिम्मेदारी से पूरी तरह से अलग आयोग की गणना के साथ, डोमेन वस्तुओं को प्राथमिकता दें:
# app/models/order.rb
class Order < ActiveRecord::Base
...
# No reference to commission calculation
end
# lib/commission.rb
class Commission
SERVICE_COMMISSION = 0.15
def self.calculate(payment_method, model)
model.amount*SERVICE_COMMISSION - payment_commission(payment_method, model)
end
private
def self.payment_commission(payment_method, model)
# There are better ways to implement a static registry,
# this is only for illustration purposes.
Object.const_get("#{payment_method}Commission").calculate(model)
end
end
# lib/stripe_commission.rb
class StripeCommission
STRIPE_PERCENTAGE_COMMISSION = 0.029
STRIPE_FIXED_COMMISSION = 0.30
def self.calculate(model)
model.amount*STRIPE_PERCENTAGE_COMMISSION
+ STRIPE_PERCENTAGE_COMMISSION
end
end
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
@order = Order.new(order_params)
@order.commission = Commission.calculate("Stripe", @order)
...
end
end
डोमेन ऑब्जेक्ट का उपयोग करने के निम्नलिखित वास्तु फायदे हैं:
- यह इकाई परीक्षण के लिए बेहद आसान है, क्योंकि किसी भी जुड़नार या कारखानों को तर्क के साथ वस्तुओं को तत्काल करने की आवश्यकता नहीं है।
- संदेश
amount
स्वीकार करता है कि सब कुछ के साथ काम करता है। - प्रत्येक डोमेन ऑब्जेक्ट को स्पष्ट रूप से परिभाषित जिम्मेदारियों के साथ, और उच्च सामंजस्य के साथ छोटा रखता है।
- नए भुगतान के तरीकों के साथ आसानी से तराजू , संशोधन नहीं ।
- रूबी एप्लिकेशन पर प्रत्येक रूबी में एक कभी-बढ़ती
User
वस्तु रखने की प्रवृत्ति को रोकता है।
मैं व्यक्तिगत रूप से डोमेन ऑब्जेक्ट्स को lib
में रखना पसंद करता हूं। यदि आप ऐसा करते हैं, तो इसे autoload_paths
जोड़ना याद रखें:
# config/application.rb
config.autoload_paths << Rails.root.join('lib')
आप कमांड / क्वेरी पैटर्न के बाद डोमेन ऑब्जेक्ट को अधिक एक्शन-ओरिएंटेड बनाना पसंद कर सकते हैं। ऐसे मामलों में, इन ऑब्जेक्ट्स को app/commands
में डालना एक बेहतर जगह हो सकती है क्योंकि सभी app
सबडायरेक्ट्रीज़ स्वतः ऑटोलॉड पथ में जुड़ जाते हैं।