Computer >> कंप्यूटर ट्यूटोरियल >  >> प्रोग्रामिंग >> Ruby

रूबी ऑन रेल्स: मॉडल पैटर्न में महारत हासिल करें और एंटी-पैटर्न से बचें

रूबी ऑन रेल्स पैटर्न और एंटी-पैटर्न श्रृंखला में दूसरे पोस्ट में आपका स्वागत है। पिछले ब्लॉग पोस्ट में, हमने इस बारे में बात की थी कि पैटर्न और एंटी-पैटर्न सामान्य रूप से क्या हैं। हमने रेल्स की दुनिया में कुछ सबसे प्रसिद्ध पैटर्न और एंटी-पैटर्न का भी उल्लेख किया था। इस ब्लॉग पोस्ट में, हम कुछ रेल्स मॉडल एंटी-पैटर्न और पैटर्न के माध्यम से जाएंगे।

यदि आप मॉडल के साथ संघर्ष कर रहे हैं, तो यह ब्लॉग पोस्ट आपके लिए है। हम जल्दी से अपने मॉडल को डाइट पर रखने की प्रक्रिया से गुजरेंगे और माइग्रेशन लिखते समय बचने योग्य कुछ चीजों के साथ मजबूती से समाप्त करेंगे। आइए सीधे इसमें कूदें।

मोटे ओवरवेट मॉडल

रेल्स एप्लिकेशन विकसित करते समय, चाहे वह एक पूर्ण रेल्स वेबसाइट हो या एक API, लोग अधिकांश तर्क को मॉडल में संग्रहीत करते हैं। पिछले ब्लॉग पोस्ट में, हमारे पास एक Song क्लास का उदाहरण था जो कई चीजें करता था। मॉडल में बहुत सारी चीजें रखना सिंगल रिस्पॉन्सिबिलिटी सिद्धांत (SRP) को तोड़ता है।

आइए एक नज़र डालते हैं।

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 है जिसका उद्देश्य गानों को विभिन्न प्रारूपों में परिवर्तित करना है। इसके अपने टेस्ट हो सकते हैं और परिवर्तित करने के बारे में भविष्य का तर्क। और, यदि हम किसी गाने को MP3 में परिवर्तित करना चाहते हैं, तो हम निम्न कर सकते हैं:

@song.converter.to_mp3

मेरे लिए, यह मॉड्यूल या कंसर्न का उपयोग करने की तुलना में थोड़ा स्पष्ट दिखता है। शायद इसलिए क्योंकि मैं विरासत पर संरचना को प्राथमिकता देता हूं। मैं इसे अधिक सहज और पठनीय मानता हूं। मेरा सुझाव है कि आप निर्णय लेने से पहले दोनों मामलों की समीक्षा करें। या आप दोनों को चुन सकते हैं अगर आप चाहते हैं, कोई आपको रोक नहीं रहा है।

SQL पास्ता पार्मेसन

वास्तविक जीवन में कुछ अच्छे पास्ता को कौन पसंद नहीं करता? दूसरी ओर, जब कोड पास्ता की बात आती है, तो लगभग कोई भी प्रशंसक नहीं है। और अच्छे कारणों से। रेल्स मॉडल में, आप अपने एक्टिव रिकॉर्ड उपयोग को जल्दी से स्पेगेटी में बदल सकते हैं, जो कोडबेस के चारों ओर घूमता रहता है। आप इससे कैसे बचते हैं?

कुछ विचार हैं जो उन लंबी क्वेरी को स्पेगेटी की लाइनों में बदलने से रोकते हैं। आइए पहले देखें कि डेटाबेस से संबंधित कोड हर जगह कैसे हो सकता है। आइए हमारे 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 नहीं है। यह एप्लिकेशन में खुद को दोहराता है। इससे आपको निराश न होने दें। सौभाग्य से, इस समस्या का समाधान है।

हम कोड को 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 रिपॉजिटरी पैटर्न नहीं है। हमारे और रेल्स रिपॉजिटरी पैटर्न के पीछे का विचार डेटाबेस तर्क को व्यावसायिक तर्क से अलग करना है। हम पूरी तरह से जा सकते हैं और एक रिपॉजिटरी क्लास बना सकते हैं जो हमारे लिए कच्चे SQL कॉल करता है एक्टिव रिकॉर्ड के बजाय, लेकिन मैं ऐसी चीजों की सिफारिश नहीं करूंगा जब तक कि आपको वास्तव में इसकी आवश्यकता न हो।

हम 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

यहां हमने क्या किया है, हमने क्वेरीइंग तर्क को एक टेस्टेबल क्लास में अलग कर दिया है। साथ ही, मॉडल अब स्कोप और तर्क से संबंधित नहीं है। कंट्रोलर और मॉडल पतले हैं, और हर कोई खुश है। सही? खैर, वहां अभी भी एक्टिव रिकॉर्ड है जो सारा भारी काम कर रहा है। हमारे परिदृश्य में, हम find का उपयोग करते हैं, जो निम्न उत्पन्न करता है:

SELECT "songs".* FROM "songs" WHERE "songs"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]

"सही" तरीका यह होगा कि यह सब SongRepository के अंदर परिभाषित हो। जैसा कि मैंने कहा, मैं इसकी सिफारिश नहीं करूंगा। आपको इसकी आवश्यकता नहीं है और आप पूर्ण नियंत्रण चाहते हैं। एक्टिव रिकॉर्ड से दूर जाने का एक उपयोग मामला यह होगा कि आपको SQL के अंदर कुछ जटिल ट्रिक्स की आवश्यकता है जो एक्टिव रिकॉर्ड द्वारा आसानी से समर्थित नहीं हैं।

कच्चे SQL और एक्टिव रिकॉर्ड के बारे में बात करते हुए, मुझे एक विषय भी उठाना होगा। माइग्रेशन का विषय और उन्हें ठीक से कैसे करना है। आइए इसमें गोता लगाएं।

माइग्रेशन - किसे परवाह है?

मैं अक्सर माइग्रेशन लिखते समय एक तर्क सुनता हूं कि वहां का कोड एप्लिकेशन के बाकी हिस्सों की तरह अच्छा नहीं होना चाहिए। और वह तर्क मुझे अच्छा नहीं लगता। लोग इस बहाने का उपयोग माइग्रेशन में बदबूदार कोड सेट करने के लिए करते हैं क्योंकि यह केवल एक बार चलेगा और भुला दिया जाएगा। शायद यह सच है यदि आप कुछ लोगों के साथ काम कर रहे हैं और हर कोई हर समय लगातार सिंक में है।

वास्तविकता अक्सर अलग होती है। एप्लिकेशन का उपयोग बड़ी संख्या में लोगों द्वारा किया जा सकता है जो नहीं जानते कि एप्लिकेशन के विभिन्न भागों के साथ क्या होता है। और यदि आप वहां कुछ संदिग्ध वन-ऑफ कोड डालते हैं, तो आप किसी के विकास वातावरण को कुछ घंटों के लिए दूषित डेटाबेस स्थिति या सिर्फ एक अजीब माइग्रेशन के कारण तोड़ सकते हैं। यकीन नहीं कि यह एक एंटी-पैटर्न है, लेकिन आपको इसके बारे में पता होना चाहिए।

दूसरे लोगों के लिए माइग्रेशन को अधिक सुविधाजनक कैसे बनाया जाए? आइए एक सूची के माध्यम से जाएं जो परियोजना पर सभी के लिए माइग्रेशन को आसान बना देगा।

सुनिश्चित करें कि आप हमेशा एक डाउन मेथड प्रदान करते हैं

आप कभी नहीं जानते कि कब कुछ रोल बैक होने वाला है। यदि आपका माइग्रेशन प्रतिवर्ती नहीं है, तो ActiveRecord::IrreversibleMigration अपवाद उठाना सुनिश्चित करें जैसे:

def down raise ActiveRecord::IrreversibleMigration end

माइग्रेशन में एक्टिव रिकॉर्ड से बचने का प्रयास करें

यहां विचार उस समय डेटाबेस की स्थिति को छोड़कर बाहरी निर्भरता को कम करना है जब माइग्रेशन निष्पादित किया जाना चाहिए। तो कोई एक्टिव रिकॉर्ड वैलिडेशन नहीं होगा जो आपके दिन को बर्बाद करे (या शायद बचाए)। आप सादे SQL के साथ रह गए हैं। उदाहरण के लिए, आइए एक माइग्रेशन लिखें जो एक निश्चित कलाकार के सभी गानों को प्रकाशित करेगा।

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 डायरेक्टरी में रख रहे हैं।

अंतिम विचार

मॉडल के साथ व्यवहार करना और उन्हें रेल्स में पठनीय रखना एक निरंतर संघर्ष है। उम्मीद है, इस ब्लॉग पोस्ट में, आपको संभावित नुकसान और सामान्य समस्याओं के समाधान देखने को मिले। इस पोस्ट में मॉडल एंटी-पैटर्न और पैटर्न की सूची पूरी नहीं है, लेकिन ये सबसे उल्लेखनीय हैं जो मुझे हाल ही में मिले।

यदि आप अधिक रेल्स पैटर्न और एंटी-पैटर्न में रुचि रखते हैं, तो श्रृंखला में अगली किस्त के लिए बने रहें। आगामी पोस्ट में, हम रेल्स MVC के व्यू और कंट्रोलर पक्ष की सामान्य समस्याओं और समाधानों के माध्यम से जाएंगे।

अगली बार तक, चीयर्स!

P.S. यदि आप रूबी मैजिक पोस्ट को प्रेस से निकलते ही पढ़ना चाहते हैं, तो हमारे रूबी मैजिक न्यूज़लेटर की सदस्यता लें और कभी भी एक भी पोस्ट मिस न करें!


  1. - रेल्स स्कैफोल्डिंग समझाया गया: तुरंत CRUD इंटरफेस बनाएं

    आप रेल्स सीख रहे होंगे और आपने पढ़ा होगा कि आपको अपना रेल्स एप्लिकेशन शुरू करने के लिए स्कैफोल्ड बनाना होगा... आसान! आप rails g scaffold कमांड का उपयोग करके ऐसा कर सकते हैं। लेकिन स्कैफोल्डिंग क्या है? “स्कैफोल्डिंग एक अस्थायी संरचना है जिसका उपयोग निर्माण, रखरखाव और इमारतों, पुलों और अन्य सभ

  1. - Ruby on Rails में Scopes को मास्टर करना: एक व्यावहारिक गाइड

    Rails में Scope क्या है और यह क्यों उपयोगी है? Scope, Rails मॉडल के अंदर scope मेथड का उपयोग करके परिभाषित की जाने वाली कस्टम क्वेरीज हैं। हर scope दो आर्गुमेंट्स लेता है: एक नाम, जिसका उपयोग आप अपने कोड में इस scope को कॉल करने के लिए करते हैं। एक लैम्ब्डा, जो क्वेरी को लागू करता है। यह कुछ इस तर

  1. Ruby on Rails में महारत: इस सिद्ध फ्रेमवर्क के साथ शक्तिशाली वेब ऐप्स तेजी से बनाएं Ruby on Rails में महारत: इस सिद्ध फ्रेमवर्क के साथ शक्तिशाली वेब ऐप्स तेजी से बनाएं

    Ruby on Rails (कभी-कभी RoR) सबसे लोकप्रिय ओपन-सोर्स वेब एप्लिकेशन फ्रेमवर्क है। यह Ruby प्रोग्रामिंग भाषा के साथ बनाया गया है। आप सिंपल से लेकर कॉम्प्लेक्स तक एप्लिकेशन बनाने में मदद के लिए Rails का उपयोग कर सकते हैं, Rails के साथ आप जो हासिल कर सकते हैं उसकी कोई सीमा नहीं है! फ्रेमवर्क क्या है? फ्र