रूबी ऑन रेल्स पैटर्न्स और एंटी-पैटर्न सीरीज की दूसरी पोस्ट में आपका स्वागत है। हमने रेल की दुनिया में कुछ सबसे प्रसिद्ध पैटर्न और विरोधी पैटर्न का भी उल्लेख किया है। इस ब्लॉग पोस्ट में, हम कुछ रेल मॉडल के एंटी-पैटर्न और पैटर्न के बारे में जानेंगे।
यदि आप मॉडल के साथ संघर्ष कर रहे हैं, तो यह ब्लॉग पोस्ट आपके लिए है। हम आपके मॉडलों को आहार पर रखने की प्रक्रिया से जल्दी से गुजरेंगे और माइग्रेशन लिखते समय बचने के लिए कुछ चीजों के साथ दृढ़ता से समाप्त करेंगे। चलो ठीक अंदर कूदें।
मोटा अधिक वजन वाले मॉडल
रेल एप्लिकेशन विकसित करते समय, चाहे वह पूरी तरह से विकसित रेल वेबसाइट हो या एपीआई, लोग मॉडल में अधिकांश तर्क संग्रहीत करते हैं। पिछले ब्लॉग पोस्ट में, हमारे पास Song
. का एक उदाहरण था वर्ग जिसने बहुत कुछ किया। मॉडल में बहुत सी चीजें रखने से एकल उत्तरदायित्व सिद्धांत (एसआरपी) टूट जाता है।
आइए एक नजर डालते हैं।
class Song < ApplicationRecord
belongs_to :album
belongs_to :artist
belongs_to :publisher
has_one :text
has_many :downloads
validates :artist_id, presence: true
validates :publisher_id, presence: true
after_update :alert_artist_followers
after_update :alert_publisher
def alert_artist_followers
return if unreleased?
artist.followers.each { |follower| follower.notify(self) }
end
def alert_publisher
PublisherMailer.song_email(publisher, self).deliver_now
end
def includes_profanities?
text.scan_for_profanities.any?
end
def user_downloaded?(user)
user.library.has_song?(self)
end
def find_published_from_artist_with_albums
...
end
def find_published_with_albums
...
end
def to_wav
...
end
def to_mp3
...
end
def to_flac
...
end
end
इस तरह के मॉडलों के साथ समस्या यह है कि वे एक गीत से संबंधित विभिन्न तर्कों के लिए डंपिंग ग्राउंड बन जाते हैं। समय के साथ एक-एक करके धीरे-धीरे जुड़ते ही तरीके जमा होने लगते हैं।
मैंने मॉडल के अंदर कोड को छोटे मॉड्यूल में विभाजित करने का सुझाव दिया। लेकिन ऐसा करके, आप बस कोड को एक स्थान से दूसरे स्थान पर ले जा रहे हैं। फिर भी, कोड को इधर-उधर घुमाने से आप कोड को बेहतर ढंग से व्यवस्थित कर सकते हैं और कम पठनीयता वाले मोटे मॉडल से बच सकते हैं।
कुछ लोग रेल की चिंताओं का भी सहारा लेते हैं और पाते हैं कि तर्क का सभी मॉडलों में पुन:उपयोग किया जा सकता है। मैंने पहले इसके बारे में लिखा था और कुछ लोगों ने इसे पसंद किया, दूसरों ने नहीं। वैसे भी, चिंताओं के साथ कहानी मॉड्यूल के समान है। आपको इस बात की जानकारी होनी चाहिए कि आप कोड को केवल मॉड्यूल में ले जा रहे हैं जिसे कहीं भी शामिल किया जा सकता है।
दूसरा विकल्प यह है कि छोटे वर्ग बनाएं और फिर जब भी आवश्यक हो उन्हें कॉल करें। उदाहरण के लिए, हम गाने को परिवर्तित करने वाले कोड को एक अलग वर्ग में निकाल सकते हैं।
class SongConverter
attr_reader :song
def initialize(song)
@song = song
end
def to_wav
...
end
def to_mp3
...
end
def to_flac
...
end
end
class Song
...
def converter
SongConverter.new(self)
end
...
end
अब हमारे पास SongConverter
है जिसका उद्देश्य गानों को अलग प्रारूप में परिवर्तित करना है। कनवर्ट करने के बारे में इसके अपने परीक्षण और भविष्य के तर्क हो सकते हैं। और, अगर हम किसी गाने को एमपी3 में बदलना चाहते हैं, तो हम निम्न कार्य कर सकते हैं:
@song.converter.to_mp3
मेरे लिए, यह मॉड्यूल या चिंता का उपयोग करने से थोड़ा स्पष्ट दिखता है। शायद इसलिए कि मैं विरासत पर रचना का उपयोग करना पसंद करता हूं। मैं इसे अधिक सहज और पठनीय मानता हूं। मेरा सुझाव है कि किस रास्ते पर जाना है, यह तय करने से पहले आप दोनों मामलों की समीक्षा करें। या आप चाहें तो दोनों को चुन सकते हैं, आपको कोई नहीं रोक रहा है।
एसक्यूएल पास्ता परमेसन
वास्तविक जीवन में कुछ अच्छे पास्ता किसे पसंद नहीं है? दूसरी ओर, जब पास्ता को कोड करने की बात आती है, तो लगभग कोई भी प्रशंसक नहीं होता है। और अच्छे कारणों से। Railsmodels में, आप अपने सक्रिय रिकॉर्ड उपयोग को जल्दी से स्पेगेटी में बदल सकते हैं, जो पूरे कोडबेस में घूमता है। आप इससे कैसे बचते हैं?
वहाँ कुछ विचार हैं जो उन लंबे प्रश्नों को स्पेगेटी की पंक्तियों में बदलने से रोकते हैं। आइए पहले देखें कि डेटाबेस से संबंधित कोड हर जगह कैसे हो सकता है। आइए अपने Song
पर वापस जाएं नमूना। विशेष रूप से, जब उसमें से कुछ लाने की कोशिश की जाती है।
class SongReportService
def gather_songs_from_artist(artist_id)
songs = Song.where(status: :published)
.where(artist_id: artist_id)
.order(:title)
...
end
end
class SongController < ApplicationController
def index
@songs = Song.where(status: :published)
.order(:release_date)
...
end
end
class SongRefreshJob < ApplicationJob
def perform
songs = Song.where(status: :published)
...
end
end
ऊपर के उदाहरण में, हमारे पास तीन उपयोग-मामले हैं जहां Song
मॉडल से पूछताछ की जा रही है। SongReporterService
. में जिसका उपयोग गीतों के बारे में डेटा रिपोर्ट करने के लिए किया जाता है, हम एक ठोस कलाकार से प्रकाशित गीत प्राप्त करने का प्रयास करते हैं। फिर, SongController
. में , हम प्रकाशित गीत प्राप्त करते हैं और उन्हें रिलीज की तारीख तक ऑर्डर करते हैं। और अंत में, SongRefreshJob
में हमें केवल प्रकाशित गीत मिलते हैं और उनके साथ कुछ करते हैं।
यह सब ठीक है, लेकिन क्या होगा यदि हम अचानक स्थिति नाम को released
में बदलने का निर्णय लेते हैं या हम गाने लाने के तरीके में कुछ और बदलाव करते हैं? हमें सभी घटनाओं को अलग-अलग जाकर संपादित करना होगा। साथ ही, उपरोक्त कोड DRY नहीं है। यह पूरे एप्लिकेशन में खुद को दोहराता है। इसे आप नीचे न आने दें। सौभाग्य से, इस समस्या के समाधान हैं।
हम रेल क्षेत्र का उपयोग कर सकते हैं इस कोड को बाहर निकालने के लिए। स्कोपिंग आपको आमतौर पर उपयोग किए जाने वाले प्रश्नों को परिभाषित करने की अनुमति देता है, जिन्हें एसोसिएशन और ऑब्जेक्ट्स पर बुलाया जा सकता है। यह हमारे कोड को पढ़ने योग्य और बदलने में आसान बनाता है। लेकिन, शायद सबसे महत्वपूर्ण बात यह है कि स्कोप हमें अन्य सक्रिय रिकॉर्ड विधियों जैसे कि joins
को श्रृंखलाबद्ध करने की अनुमति देता है , where
,आदि। आइए देखें कि हमारा कोड स्कोप के साथ कैसा दिखता है।
class Song < ApplicationRecord
...
scope :published, -> { where(published: true) }
scope :by_artist, ->(artist_id) { where(artist_id: artist_id) }
scope :sorted_by_title, { order(:title) }
scope :sorted_by_release_date, { order(:release_date) }
...
end
class SongReportService
def gather_songs_from_artist(artist_id)
songs = Song.published.by_artist(artist_id).sorted_by_title
...
end
end
class SongController < ApplicationController
def index
@songs = Song.published.sorted_by_release_date
...
end
end
class SongRefreshJob < ApplicationJob
def perform
songs = Song.published
...
end
end
तुम वहाँ जाओ। हम दोहराने वाले कोड को काटने और मॉडल में डालने में कामयाब रहे। लेकिन यह हमेशा सर्वश्रेष्ठ के लिए काम नहीं करता है, खासकर यदि आपको एक मोटे मॉडल या एक गॉड ऑब्जेक्ट के मामले का निदान किया जाता है। मॉडल में अधिक से अधिक तरीकों और जिम्मेदारियों को जोड़ना इतना अच्छा विचार नहीं हो सकता है।
यहां मेरी सलाह है कि स्कोप के उपयोग को न्यूनतम रखें और वहां केवल सामान्य प्रश्नों को ही निकालें। हमारे मामले में, शायद where(published: true)
यह एकदम सही गुंजाइश होगी क्योंकि इसका इस्तेमाल हर जगह किया जाता है। अन्य SQL संबंधित कोड के लिए, आप रिपोजिटरी पैटर्न नामक किसी चीज़ का उपयोग कर सकते हैं। आइए जानें कि यह क्या है।
भंडार पैटर्न
हम जो दिखाने जा रहे हैं वह डोमेन-संचालित डिज़ाइन बुक में परिभाषित 1:1 रिपोजिटरी पैटर्न नहीं है। हमारे और रेल रिपोजिटरी पैटर्न के पीछे का विचार डेटाबेस लॉजिक को व्यावसायिक तर्क से अलग करना है। हम फुल-ऑन भी जा सकते हैं और एक रिपॉजिटरी क्लास बना सकते हैं जो एक्टिव रिकॉर्ड के बजाय हमारे लिए रॉ एसक्यूएल कॉल करता है, लेकिन मैं ऐसी चीजों की सिफारिश नहीं करूंगा जब तक कि आपको वास्तव में इसकी आवश्यकता न हो।
हम क्या कर सकते हैं एक SongRepository
. बना सकते हैं और वहां डेटाबेस लॉजिक डालें।
class SongRepository
class << self
def find(id)
Song.find(id)
rescue ActiveRecord::RecordNotFound => e
raise RecordNotFoundError, e
end
def destroy(id)
find(id).destroy
end
def recently_published_by_artist(artist_id)
Song.where(published: true)
.where(artist_id: artist_id)
.order(:release_date)
end
end
end
class SongReportService
def gather_songs_from_artist(artist_id)
songs = SongRepository.recently_published_by_artist(artist_id)
...
end
end
class SongController < ApplicationController
def destroy
...
SongRepository.destroy(params[:id])
...
end
end
हमने यहां क्या किया है कि हमने क्वेरीिंग लॉजिक को एक परीक्षण योग्य वर्ग में अलग कर दिया है। साथ ही, मॉडल अब स्कोप और लॉजिक से संबंधित नहीं है। नियंत्रक और मॉडल पतले हैं, और हर कोई खुश है। सही? खैर, अभी भी ActiveRecord वहां सभी भारी खींच रहा है। हमारे परिदृश्य में, हम find
. का उपयोग करते हैं , जो निम्नलिखित उत्पन्न करता है:
SELECT "songs".* FROM "songs" WHERE "songs"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
"सही" तरीका यह होगा कि यह सब SongRepository
. के अंदर परिभाषित किया जाए . जैसा कि मैंने कहा, मैं इसकी अनुशंसा नहीं करूंगा। आपको इसकी आवश्यकता नहीं है और आप पूर्ण नियंत्रण रखना चाहते हैं। सक्रिय रिकॉर्ड से दूर जाने के लिए एक उपयोग का मामला यह होगा कि आपको SQL के अंदर कुछ जटिल तरकीबें चाहिए जो आसानी से सक्रिय रिकॉर्ड द्वारा समर्थित नहीं हैं।
कच्चे एसक्यूएल और सक्रिय रिकॉर्ड के बारे में बात करते हुए, मुझे एक विषय भी लाना है। प्रवासन का विषय और उन्हें ठीक से कैसे करना है। आइए इसमें डुबकी लगाते हैं।
माइग्रेशन — कौन परवाह करता है?
माइग्रेशन लिखते समय मैं अक्सर एक तर्क सुनता हूं कि वहां का कोड उतना अच्छा नहीं होना चाहिए जितना कि बाकी एप्लिकेशन में है। और वह तर्क मुझे अच्छा नहीं लगता। लोग प्रवास में बदबूदार कोड सेट करने के लिए इस बहाने का उपयोग करते हैं क्योंकि इसे केवल एक बार चलाया जाएगा और भुला दिया जाएगा। हो सकता है कि यह सच हो अगर आप कुछ लोगों के साथ काम कर रहे हैं और हर कोई हर समय लगातार सिंक कर रहा है।
वास्तविकता अक्सर अलग होती है। एप्लिकेशन का उपयोग बड़ी संख्या में लोगों द्वारा किया जा सकता है जो यह नहीं जानते कि विभिन्न एप्लिकेशन भागों के साथ क्या होता है। और अगर आप वहां कुछ संदिग्ध वन-ऑफ कोड डालते हैं, तो आप दूषित डेटाबेस स्थिति या सिर्फ एक अजीब माइग्रेशन के कारण किसी के विकास के माहौल को कुछ घंटों के लिए तोड़ सकते हैं। सुनिश्चित नहीं है कि यह एक विरोधी पैटर्न है, लेकिन आपको इसके बारे में पता होना चाहिए।
अन्य लोगों के लिए प्रवासन को और अधिक सुविधाजनक कैसे बनाया जाए? आइए एक ऐसी सूची देखें जो परियोजना में सभी के लिए माइग्रेशन को आसान बनाएगी।
सुनिश्चित करें कि आप हमेशा डाउन मेथड प्रदान करते हैं
आप कभी नहीं जानते कि कब कुछ वापस लुढ़कने वाला है। यदि आपका माइग्रेशन प्रतिवर्ती नहीं है, तो ActiveRecord::IrreversibleMigration
बढ़ाना सुनिश्चित करें ऐसा अपवाद:
def down
raise ActiveRecord::IrreversibleMigration
end
माइग्रेशन में सक्रिय रिकॉर्ड से बचने की कोशिश करें
यहाँ विचार बाहरी निर्भरता को कम करने के लिए है, उस समय डेटाबेस की स्थिति को छोड़कर जब माइग्रेशन निष्पादित किया जाना चाहिए। तो आपके दिन को बर्बाद करने (या शायद बचाने) के लिए कोई सक्रिय रिकॉर्ड सत्यापन नहीं होगा। आप सादे एसक्यूएल के साथ बचे हैं। उदाहरण के लिए, चलिए एक माइग्रेशन लिखते हैं जो किसी खास कलाकार के सभी गानों को प्रकाशित करेगा।
class UpdateArtistsSongsToPublished < ActiveRecord::Migration[6.0]
def up
execute <<-SQL
UPDATE songs
SET published = true
WHERE artist_id = 46
SQL
end
def down
execute <<-SQL
UPDATE songs
SET published = false
WHERE artist_id = 46
SQL
end
end
यदि आपको Song
की बहुत आवश्यकता है मॉडल, एक सुझाव यह होगा कि इसे माइग्रेशन के अंदर परिभाषित किया जाए। इस तरह, आप app/models
के अंदर वास्तविक सक्रिय रिकॉर्ड मॉडल में किसी भी संभावित परिवर्तन से अपने माइग्रेशन को बुलेटप्रूफ कर सकते हैं .लेकिन, क्या यह सब ठीक है और बांका है? आइए अपने अगले बिंदु पर चलते हैं।
स्कीमा माइग्रेशन को डेटा माइग्रेशन से अलग करें
माइग्रेशन पर रेल गाइड्स के माध्यम से जाने पर, आप निम्नलिखित पढ़ेंगे:
<ब्लॉककोट>माइग्रेशन सक्रिय रिकॉर्ड की एक विशेषता है जो आपको अपना डेटाबेस स्कीमा . विकसित करने की अनुमति देता है अधिक समय तक। शुद्ध SQL में स्कीमा संशोधन लिखने के बजाय, माइग्रेशन आपको अपनी तालिकाओं में परिवर्तनों का वर्णन करने के लिए रूबी DSL का उपयोग करने की अनुमति देता है।
गाइड के सारांश में, डेटाबेस तालिका के वास्तविक डेटा को संपादित करने का कोई उल्लेख नहीं है, केवल संरचना है। इसलिए, यह तथ्य कि हमने दूसरे बिंदु में गानों को अपडेट करने के लिए नियमित माइग्रेशन का इस्तेमाल किया, पूरी तरह से सही नहीं है।
यदि आपको अपने प्रोजेक्ट में नियमित रूप से कुछ ऐसा ही करने की आवश्यकता है, तो data_migrate
. का उपयोग करने पर विचार करें रत्न यह डेटा माइग्रेशन को स्कीमा माइग्रेशन से अलग करने का एक अच्छा तरीका है। हम इसके साथ अपने पिछले उदाहरण को आसानी से फिर से लिख सकते हैं। डेटा माइग्रेशन जेनरेट करने के लिए, हम निम्न कार्य कर सकते हैं:
bin/rails generate data_migration update_artists_songs_to_published
और फिर वहां माइग्रेशन लॉजिक जोड़ें:
class UpdateArtistsSongsToPublished < ActiveRecord::Migration[6.0]
def up
execute <<-SQL
UPDATE songs
SET published = true
WHERE artist_id = 46
SQL
end
def down
execute <<-SQL
UPDATE songs
SET published = false
WHERE artist_id = 46
SQL
end
end
इस तरह, आप अपने सभी स्कीमा माइग्रेशन को db/migrate
. के अंदर रख रहे हैं निर्देशिका और सभी माइग्रेशन जो db/data
. के अंदर डेटा से संबंधित हैं निर्देशिका।
अंतिम विचार
मॉडलों के साथ व्यवहार करना और उन्हें आसानी से रेल में रखना एक निरंतर संघर्ष है। उम्मीद है, इस ब्लॉग पोस्ट में, आपको आम समस्याओं के संभावित नुकसान और समाधान देखने को मिलेंगे। मॉडल विरोधी पैटर्न और पैटर्न की सूची इस पोस्ट में पूरी नहीं है, लेकिन ये सबसे उल्लेखनीय हैं जिन्हें मैंने हाल ही में पाया है।
यदि आप अधिक रेल पैटर्न और एंटी-पैटर्न में रुचि रखते हैं, तो श्रृंखला में अगली किस्त के लिए बने रहें। आगामी पोस्ट में, हम रेल एमवीसी के व्यू और कंट्रोलर साइड की सामान्य समस्याओं और समाधानों के बारे में जानेंगे।
अगली बार तक, चीयर्स!
पी.एस. यदि आप रूबी मैजिक की पोस्ट प्रेस से बाहर होते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूजलेटर की सदस्यता लें और एक भी पोस्ट मिस न करें!