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

रूबी ऑन रेल्स मॉडल पैटर्न और एंटी-पैटर्न

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

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

मोटा अधिक वजन वाले मॉडल

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

अंतिम विचार

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

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

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

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


  1. रूबी ऑन रेल्स में मचान क्या है?

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

  1. रूबी ऑन रेल्स में स्कोप का उपयोग कैसे करें

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

  1. रूबी ऑन रेल्स क्या है और यह क्यों उपयोगी है?

    रूबी ऑन रेल्स (कभी-कभी RoR) सबसे लोकप्रिय ओपन-सोर्स वेब एप्लिकेशन फ्रेमवर्क है। इसे रूबी प्रोग्रामिंग भाषा के साथ बनाया गया है। आप अनुप्रयोगों को बनाने में मदद करने के लिए रेल का उपयोग कर सकते हैं, सरल से जटिल तक, रेल के साथ आप क्या हासिल कर सकते हैं इसकी कोई सीमा नहीं है! ढांचा क्या है? फ़्रेम