কম্পিউটার

রুবি অন রেল মডেল প্যাটার্নস এবং অ্যান্টি-প্যাটার্নস

রুবি অন রেইলস প্যাটার্নস এবং অ্যান্টি-প্যাটার্ন সিরিজের দ্বিতীয় পোস্টে আবার স্বাগতম। শেষ ব্লগ পোস্টে, আমরা সাধারণভাবে কী প্যাটার্ন এবং অ্যান্টি-প্যাটার্নস তা দেখেছিলাম। আমরা রেল জগতের কিছু বিখ্যাত নিদর্শন এবং অ্যান্টি-প্যাটার্নের কথাও উল্লেখ করেছি। এই ব্লগ পোস্টে, আমরা একগুচ্ছ রেল মডেল অ্যান্টি-প্যাটার্ন এবং প্যাটার্নের মধ্য দিয়ে যাব।

আপনি যদি মডেলগুলির সাথে লড়াই করে থাকেন তবে এই ব্লগ পোস্টটি আপনার জন্য। আমরা দ্রুত আপনার মডেলগুলিকে ডায়েটে রাখার প্রক্রিয়ার মধ্য দিয়ে যাব এবং মাইগ্রেশন লেখার সময় এড়াতে কিছু জিনিস দিয়ে দৃঢ়ভাবে শেষ করব। আসুন সরাসরি ভিতরে ঝাঁপ দেওয়া যাক।

ফ্যাট অতিরিক্ত ওজনের মডেল

একটি রেল অ্যাপ্লিকেশন ডেভেলপ করার সময়, এটি একটি পূর্ণ-বিকশিত রেল ওয়েবসাইট বা একটি 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 পাস্তা পারমেসান

বাস্তব জীবনে কিছু ভালো পাস্তা কে না ভালোবাসে? অন্যদিকে, যখন কোড পাস্তার কথা আসে, তখন প্রায় কেউই ভক্ত নয়। এবং ভাল কারণে. 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) নিখুঁত সুযোগ হবে যেহেতু এটি সর্বত্র ব্যবহৃত হয়। অন্যান্য এসকিউএল সম্পর্কিত কোডের জন্য, আপনি রিপোজিটরি প্যাটার্ন নামে কিছু ব্যবহার করতে পারেন। আসুন জেনে নেই এটা কি।

রিপোজিটরি প্যাটার্ন

আমরা যা দেখাতে যাচ্ছি তা ডোমেন-চালিত ডিজাইন বইতে সংজ্ঞায়িত 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

আমরা এখানে যা করেছি তা হল আমরা একটি পরীক্ষাযোগ্য শ্রেণীতে অনুসন্ধানের যুক্তিকে বিচ্ছিন্ন করেছি৷ এছাড়াও, মডেলটি আর স্কোপ এবং যুক্তির সাথে সম্পর্কিত নয়৷ কন্ট্রোলার এবং মডেলগুলি পাতলা, এবং সবাই খুশি। ঠিক? ওয়েল, এখনও ActiveRecord সেখানে সব ভারী pulling করছেন. আমাদের দৃশ্যে, আমরা find ব্যবহার করি , যা নিম্নলিখিতগুলি তৈরি করে:

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

"সঠিক" উপায় হল এই সমস্ত SongRepository-এর ভিতরে সংজ্ঞায়িত করা . আমি যেমন বলেছি, আমি এটি সুপারিশ করব না। আপনার এটির প্রয়োজন নেই এবং আপনি সম্পূর্ণ নিয়ন্ত্রণ করতে চান। অ্যাক্টিভ রেকর্ড থেকে দূরে যাওয়ার জন্য একটি ব্যবহারের ক্ষেত্রে এসকিউএল-এর মধ্যে আপনার কিছু জটিল কৌশল প্রয়োজন যা অ্যাক্টিভ রেকর্ড দ্বারা সহজে সমর্থিত নয়।

কাঁচা এসকিউএল এবং সক্রিয় রেকর্ড সম্পর্কে কথা বলতে, আমাকে একটি বিষয়ও আনতে হবে। মাইগ্রেশনের বিষয় এবং কীভাবে সেগুলি সঠিকভাবে করা যায়। চলো ডুব দিই।

মাইগ্রেশন — কে যত্ন করে?

মাইগ্রেশন লেখার সময় আমি প্রায়ই একটি যুক্তি শুনতে পাই যে কোডটি বাকি অ্যাপ্লিকেশনের মতো ভাল হওয়া উচিত নয়। এবং এই যুক্তি আমার সাথে ভাল হয় না. লোকেরা থিমগ্রেশনে দুর্গন্ধযুক্ত কোড সেট আপ করার জন্য এই অজুহাতটি ব্যবহার করে কারণ এটি শুধুমাত্র একবার চালানো হবে এবং ভুলে যাবে। আপনি যদি কিছু লোকের সাথে কাজ করেন এবং প্রত্যেকে সবসময় অসংলগ্ন সিঙ্কে থাকে তবে এটি সত্য হতে পারে৷

বাস্তবতা প্রায়শই ভিন্ন হয়। অ্যাপ্লিকেশনটি একটি বৃহত্তর সংখ্যক লোক ব্যবহার করতে পারে যারা বিভিন্ন অ্যাপ্লিকেশন অংশগুলির সাথে কী ঘটবে তা জানেন না। এবং যদি আপনি সেখানে কিছু সন্দেহজনক এক-অফ কোড রাখেন, তাহলে আপনি নষ্ট হয়ে যাওয়া ডাটাবেস অবস্থার কারণে বা শুধুমাত্র একটি অদ্ভুত স্থানান্তরের কারণে কয়েক ঘন্টার জন্য কারও উন্নয়ন পরিবেশ ভেঙে দিতে পারেন। এটি একটি অ্যান্টি-প্যাটার্ন কিনা তা নিশ্চিত নই, তবে আপনার এটি সম্পর্কে সচেতন হওয়া উচিত৷

কিভাবে অন্য মানুষের জন্য মাইগ্রেশন আরো সুবিধাজনক করতে? আসুন একটি তালিকা দিয়ে যাই যা প্রকল্পের প্রত্যেকের জন্য স্থানান্তরকে সহজ করে তুলবে৷

নিশ্চিত করুন যে আপনি সর্বদা একটি ডাউন পদ্ধতি প্রদান করেন

আপনি কখনই জানেন না যে কখন কিছু ফিরিয়ে আনা হবে। যদি আপনার মাইগ্রেশন প্রত্যাবর্তনযোগ্য না হয়, তাহলে 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-এ স্কিমা পরিবর্তনগুলি লেখার পরিবর্তে, স্থানান্তরগুলি আপনাকে আপনার টেবিলের পরিবর্তনগুলি বর্ণনা করতে একটি রুবি ডিএসএল ব্যবহার করার অনুমতি দেয়৷

গাইডের সারাংশে, ডাটাবেস টেবিলের প্রকৃত ডেটা সম্পাদনা করার কোন উল্লেখ নেই, শুধুমাত্র কাঠামো। সুতরাং, দ্বিতীয় পয়েন্টে গান আপডেট করার জন্য আমরা যে নিয়মিত মাইগ্রেশন ব্যবহার করেছি তা পুরোপুরি সঠিক নয়।

আপনি যদি নিয়মিত আপনার প্রকল্পে অনুরূপ কিছু করতে চান, তাহলে 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 এর ভিউ এবং কন্ট্রোলার সাইডের সাধারণ সমস্যা এবং সমাধানগুলির মধ্য দিয়ে যাব৷

পরের বার পর্যন্ত, চিয়ার্স!

পি.এস. আপনি যদি রুবি ম্যাজিক পোস্টগুলি প্রেস থেকে বের হওয়ার সাথে সাথে পড়তে চান তবে আমাদের রুবি ম্যাজিক নিউজলেটারে সাবস্ক্রাইব করুন এবং একটি পোস্ট মিস করবেন না!


  1. রুবি নিউজ ব্রিফ (Q2 2021)

  2. রেল অন রুবি মধ্যে ভারা কি?

  3. রেলে রুবিতে কীভাবে স্কোপ ব্যবহার করবেন

  4. রেলে রুবি কি এবং কেন এটি দরকারী?