কম্পিউটার

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

রুবি অন রেল প্যাটার্নস এবং অ্যান্টি-প্যাটার্নস সিরিজের চতুর্থ কিস্তিতে আবার স্বাগতম।

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

সামনের লাইনে

যেহেতু রুবি অন রেল একটি ওয়েব ফ্রেমওয়ার্ক, তাই HTTP অনুরোধগুলি এটির একটি গুরুত্বপূর্ণ অংশ। সমস্ত ধরণের ক্লায়েন্টরা রেল ব্যাকএন্ডের ভিয়ারেকোয়েস্টে পৌঁছায় এবং এখানেই কন্ট্রোলাররা জ্বলজ্বল করে। কন্ট্রোলাররা অনুরোধ গ্রহণ এবং পরিচালনার প্রথম সারিতে থাকে। এটি তাদের রুবি অন রেল ফ্রেমওয়ার্কের একটি মৌলিক অংশ করে তোলে। অবশ্যই, এমন কিছু কোড আছে যা কন্ট্রোলারের আগে আসে, কিন্তু কন্ট্রোলার কোড এমন কিছু যা আমাদের বেশিরভাগই নিয়ন্ত্রণ করতে পারে।

একবার আপনি config/routes.rb এ রুট সংজ্ঞায়িত করুন , আপনি সেট রুটে সার্ভারে আঘাত করতে পারেন, এবং সংশ্লিষ্ট নিয়ামক বাকিটির যত্ন নেবে। পূর্ববর্তী বাক্যটি পড়া একটি ধারণা দিতে পারে যে সবকিছুই সহজ। কিন্তু, প্রায়শই, অনেক ওজন নিয়ন্ত্রণকারীর কাঁধে পড়ে৷ সেখানে প্রমাণীকরণ এবং অনুমোদনের উদ্বেগ থাকে, তারপরে প্রয়োজনীয় ডেটা কীভাবে আনতে হয়, সেইসাথে কোথায় এবং কীভাবে ব্যবসায়িক যুক্তি সম্পাদন করতে হয় সেগুলির সমস্যা রয়েছে৷

এই সমস্ত উদ্বেগ এবং দায়িত্ব যা কন্ট্রোলারের ভিতরে ঘটতে পারে কিছু অ্যান্টি-প্যাটার্নের দিকে নিয়ে যেতে পারে। সবচেয়ে 'বিখ্যাত'গুলির মধ্যে একটি হল "ফ্যাট" কন্ট্রোলারের বিরোধী-প্যাটার্ন৷

চর্বি (স্থূল) কন্ট্রোলার

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

একজন নিয়ন্ত্রকের কী করা উচিত সে সম্পর্কে অনেক মতামত রয়েছে। একজন নিয়ন্ত্রকের দায়িত্বগুলির একটি সাধারণ ভিত্তি নিম্নলিখিতগুলি অন্তর্ভুক্ত করা উচিত:

  • প্রমাণিকরণ এবং অনুমোদন — অনুরোধের পিছনে থাকা সত্তা (প্রায়শই, একজন ব্যবহারকারী) এটি কে বলেছে কিনা এবং এটিকে সংস্থান অ্যাক্সেস করার বা ক্রিয়া সম্পাদন করার অনুমতি দেওয়া হয়েছে কিনা তা পরীক্ষা করা। প্রায়শই, সেশন বা কুকিতে প্রমাণীকরণ করা হয়, কিন্তু নিয়ামককে এখনও যাচাই করা উচিত যে প্রমাণীকরণ ডেটা এখনও বৈধ কিনা।
  • ডেটা আনা হচ্ছে - অনুরোধের সাথে আসা পরামিতিগুলির উপর ভিত্তি করে সঠিক ডেটা খোঁজার জন্য এটি যুক্তিকে কল করা উচিত। নিখুঁত বিশ্বে, এটি একটি পদ্ধতিতে কল করা উচিত যা সমস্ত কাজ করে। কন্ট্রোলারের ভারী কাজ করা উচিত নয়, এটি আরও অর্পণ করা উচিত।
  • টেমপ্লেট রেন্ডারিং — পরিশেষে, সঠিক বিন্যাস (HTML, JSON, ইত্যাদি) সহ ফলাফল রেন্ডার করে এটি সঠিক প্রতিক্রিয়া প্রদান করবে। অথবা, এটি অন্য কোনো পাথ বা URL-এ রিডাইরেক্ট করা উচিত।

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

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

কোয়েরি অবজেক্ট

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

বেশিরভাগ ক্ষেত্রে, ক্যোয়ারী অবজেক্ট হল একটি প্লেইন ওল্ড রুবি অবজেক্ট যা ActiveRecord দিয়ে আরম্ভ করা হয় সম্পর্ক একটি সাধারণ ক্যোয়ারী অবজেক্ট এইরকম দেখতে পারে:

# app/queries/all_songs_query.rb
 
class AllSongsQuery
  def initialize(songs = Song.all)
    @songs = songs
  end
 
  def call(params, songs = Song.all)
    songs.where(published: true)
         .where(artist_id: params[:artist_id])
         .order(:title)
  end
end

এটি এমনভাবে কন্ট্রোলারের ভিতরে ব্যবহার করার জন্য তৈরি করা হয়েছে:

class SongsController < ApplicationController
  def index
    @songs = AllSongsQuery.new.call(all_songs_params)
  end
 
  private
 
  def all_songs_params
    params.slice(:artist_id)
  end
end

আপনি ক্যোয়ারী অবজেক্টের অন্য পদ্ধতিও চেষ্টা করে দেখতে পারেন:

# app/queries/all_songs_query.rb
 
class AllSongsQuery
  attr_reader :songs
 
  def initialize(songs = Song.all)
    @songs = songs
  end
 
  def call(params = {})
    scope = published(songs)
    scope = by_artist_id(scope, params[:artist_id])
    scope = order_by_title(scope)
  end
 
  private
 
  def published(scope)
    scope.where(published: true)
  end
 
  def by_artist_id(scope, artist_id)
    artist_id ? scope.where(artist_id: artist_id) : scope
  end
 
  def order_by_title(scope)
    scope.order(:title)
  end
end

পরবর্তী পদ্ধতিটি params তৈরি করে ক্যোয়ারী অবজেক্টটিকে আরও শক্তিশালী করে তোলে ঐচ্ছিক এছাড়াও, লক্ষ্য করুন যে আমরা এখন AllSongsQuery.new.call কল করতে পারি আপনি যদি এর বড় ভক্ত না হন তবে আপনি ক্লাস পদ্ধতি অবলম্বন করতে পারেন। আপনি যদি আপনার ক্যোয়ারী ক্লাসটি ক্লাস পদ্ধতির সাথে লেখেন তবে এটি আর একটি 'অবজেক্ট' হবে না, তবে এটি ব্যক্তিগত স্বাদের বিষয়। দৃষ্টান্তের উদ্দেশ্যে, আসুন দেখি কিভাবে আমরা AllSongsQuery তৈরি করতে পারি বন্য মধ্যে কল করা সহজ.

# app/queries/all_songs_query.rb
 
class AllSongsQuery
  class << self
    def call(params = {}, songs = Song.all)
      scope = published(songs)
      scope = by_artist_id(scope, params[:artist_id])
      scope = order_by_title(scope)
    end
 
    private
 
    def published(scope)
      scope.where(published: true)
    end
 
    def by_artist_id(scope, artist_id)
      artist_id ? scope.where(artist_id: artist_id) : scope
    end
 
    def order_by_title(scope)
      scope.order(:title)
    end
  end
end

এখন, আমরা AllSongsQuery.call কল করতে পারি এবং আমরা সম্পন্ন করেছি। আমরা params এ পাস করতে পারি artist_id সহ . এছাড়াও, কোনো কারণে যদি আমাদের এটি পরিবর্তন করার প্রয়োজন হয় তবে আমরা প্রাথমিক সুযোগটি অতিক্রম করতে পারি। আপনি যদি সত্যিই new কল করা এড়াতে চান একটি ক্যোয়ারী ক্লাসে, এই 'ট্রিক' ব্যবহার করে দেখুন:

# app/queries/application_query.rb
 
class ApplicationQuery
  def self.call(*params)
    new(*params).call
  end
end

আপনি ApplicationQuery তৈরি করতে পারেন এবং তারপরে অন্যান্য কোয়েরি ক্লাসে এটি থেকে উত্তরাধিকারী করুন:

# app/queries/all_songs_query.rb
class AllSongsQuery < ApplicationQuery
  ...
end

আপনি এখনও AllSongsQuery.call রেখেছেন , কিন্তু আপনি এটাকে আরো মার্জিত করে তুলেছেন।

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

পরিষেবার জন্য প্রস্তুত

ঠিক আছে, তাই আমরা QueryObjects-এ ডেটা সংগ্রহ এবং আনয়ন অর্পণ করার উপায়গুলি পরিচালনা করেছি৷ তথ্য সংগ্রহ এবং যেখানে আমরা এটি রেন্ডার করি তার মধ্যে পিলড-আপ লজিক দিয়ে আমরা কী করব? আপনি যা জিজ্ঞাসা করেছেন তা ভাল, কারণ একটি সমাধান হল পরিষেবাগুলিকে ব্যবহার করা। একটি পরিষেবা প্রায়ই একটি PORO (প্লেইন ওল্ড রুবি অবজেক্ট) হিসাবে বিবেচিত হয় যা একটি একক (ব্যবসায়িক) ক্রিয়া সম্পাদন করে। আমরা এগিয়ে যাব এবং এই ধারণাটি একটু নীচে অন্বেষণ করব৷

কল্পনা করুন আমাদের দুটি পরিষেবা রয়েছে। একজন একটি রসিদ তৈরি করে, অন্যটি ব্যবহারকারীর কাছে একটি রসিদ পাঠায় এভাবে:

# app/services/create_receipt_service.rb
class CreateReceiptService
  def self.call(total, user_id)
    Receipt.create!(total: total, user_id: user_id)
  end
end
 
# app/services/send_receipt_service.rb
class SendReceiptService
  def self.call(receipt)
    UserMailer.send_receipt(receipt).deliver_later
  end
end

তারপর, আমাদের কন্ট্রোলারে আমরা SendReceiptService কল করব এই মত:

# app/controllers/receipts_controller.rb
 
class ReceiptsController < ApplicationController
  def create
    receipt = CreateReceiptService.call(total: receipt_params[:total],
                                        user_id: receipt_params[:user_id])
 
    SendReceiptService.call(receipt)
  end
end

এখন আপনার কাছে দুটি পরিষেবা রয়েছে যা সমস্ত কাজ করছে এবং নিয়ামক কেবল তাদের কল করে। আপনি এগুলি আলাদাভাবে পরীক্ষা করতে পারেন, কিন্তু সমস্যা হল, পরিষেবাগুলির মধ্যে স্পষ্ট সংযোগ নেই৷ হ্যাঁ, তাত্ত্বিকভাবে, তাদের সকলেই একক ব্যবসায়িক ক্রিয়া সম্পাদন করে। কিন্তু, যদি আমরা স্টেকহোল্ডারদের দৃষ্টিকোণ থেকে বিমূর্ততা স্তর বিবেচনা করি - একটি রসিদ তৈরি করার ক্রিয়া সম্পর্কে তাদের দৃষ্টিভঙ্গি এটির একটি ইমেল পাঠানো জড়িত। কার বিমূর্ততার স্তর 'সঠিক'™️?

এই চিন্তা পরীক্ষাটিকে আরও জটিল করার জন্য, আসুন একটি প্রয়োজনীয়তা যোগ করি যে রসিদের মোট যোগফল রসিদ তৈরির সময় কোথাও থেকে গণনা বা আনতে হবে। তখন আমরা কি করব? মোট যোগফলের যোগফল পরিচালনা করতে অন্য একটি পরিষেবা লিখুন? উত্তরটি হতে পারে একক দায়িত্ব নীতি (এসআরপি) অনুসরণ করা এবং একে অপরের থেকে বিমূর্ত জিনিসগুলিকে দূরে রাখা।

# app/services/create_receipt_service.rb
class CreateReceiptService
  ...
end
 
# app/services/send_receipt_service.rb
class SendReceiptService
  ...
end
 
# app/services/calculate_receipt_total_service.rb
class CalculateReceiptTotalService
  ...
end
 
# app/controllers/receipts_controller.rb
class ReceiptsController < ApplicationController
  def create
    total = CalculateReceiptTotalService.call(user_id: receipts_controller[:user_id])
 
    receipt = CreateReceiptService.call(total: total,
                                        user_id: receipt_params[:user_id])
 
    SendReceiptService.call(receipt)
  end
end

এসআরপি অনুসরণ করে, আমরা নিশ্চিত করি যে আমাদের পরিষেবাগুলিকে একত্রে বৃহত্তর বিমূর্ততায় তৈরি করা যেতে পারে, যেমন ReceiptCreation প্রক্রিয়া এই 'প্রসেস' ক্লাস তৈরি করে, আমরা প্রক্রিয়াটি সম্পূর্ণ করার জন্য প্রয়োজনীয় সমস্ত ক্রিয়াগুলিকে গোষ্ঠীবদ্ধ করতে পারি। এই ধারণা সম্পর্কে যুবকদের কী মনে হয়? এটি প্রথমে খুব বেশি বিমূর্ততার মতো শোনাতে পারে, কিন্তু আপনি যদি এই সমস্ত ক্রিয়াকলাপগুলিকে সর্বত্র কল করেন তবে এটি উপকারী প্রমাণিত হতে পারে৷ যদি এটি আপনার কাছে ভাল মনে হয় তবে ট্রেলব্লেজারের অপারেশনটি দেখুন৷

সংক্ষেপে, নতুন CalculateReceiptTotalService সেবা সব তারপর সংখ্যা crunching মোকাবেলা করতে পারেন. আমাদের CreateReceiptService ডাটাবেসে রসিদ লেখার জন্য দায়ী। SendReceiptService ব্যবহারকারীদের তাদের রসিদ সম্পর্কে ইমেল পাঠানোর জন্য আছে। এই ছোট এবং ফোকাস করা ক্লাসগুলি অন্যান্য ব্যবহারের ক্ষেত্রে এগুলিকে একত্রিত করা সহজ করে তুলতে পারে, ফলে কোডবেস রক্ষণাবেক্ষণ করা সহজ এবং পরীক্ষা করা সহজ হয়৷

দ্যা সার্ভিস ব্যাকস্টোরি

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

  • কমান্ডের নাম
  • অবজেক্ট/ক্লাস কমান্ডে কল করার পদ্ধতির নাম
  • পদ্ধতি পরামিতিগুলির জন্য মানগুলি পাস করতে হবে

সুতরাং, আমাদের ক্ষেত্রে, একটি কমান্ডের কলার একজন নিয়ামক। পন্থা খুবই অনুরূপ, শুধু যে রুবির নামকরণ হল 'পরিষেবা'।

কাজ বিভক্ত করুন

যদি আপনার কন্ট্রোলাররা কিছু 3য় পক্ষের পরিষেবাগুলিতে কল করে এবং তারা আপনার রেন্ডারিংকে ব্লক করে, তাহলে হয়ত এই কলগুলি বের করার এবং অন্য কন্ট্রোলার অ্যাকশনের সাথে আলাদাভাবে রেন্ডার করার সময় এসেছে৷ এর একটি উদাহরণ হতে পারে যখন আপনি একটি বইয়ের তথ্য রেন্ডার করার চেষ্টা করেন এবং অন্য কোনো পরিষেবা থেকে এর রেটিং আনতে চান যা আপনি সত্যিই প্রভাবিত করতে পারেন না (যেমন গুডরিডস)।

# app/controllers/books_controller.rb
 
class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
 
    @rating = GoodreadsRatingService.new(book).call
  end
end

গুডরিডস ডাউন হলে বা অনুরূপ কিছু হলে, আপনার ব্যবহারকারীদের গুডরিড সার্ভারের অনুরোধের সময় শেষ হওয়ার জন্য অপেক্ষা করতে হবে। অথবা, যদি তাদের সার্ভারে কিছু ধীর হয়, পৃষ্ঠাটি ধীরে ধীরে লোড হবে। আপনি 3য় পক্ষের পরিষেবার কলিংকে অন্য একটি অ্যাকশনে বের করতে পারেন যেমন:

# app/controllers/books_controller.rb
 
class BooksController < ApplicationController
  ...
 
  def show
    @book = Book.find(params[:id])
  end
 
  def rating
    @rating = GoodreadsRatingService.new(@book).call
 
    render partial: 'book_rating'
  end
 
  ...
end

তারপর, আপনাকে rating কল করতে হবে আপনার দৃষ্টিভঙ্গি থেকে পথ, কিন্তু আরে, আপনার শো-অ্যাকশনে আর ব্লকার নেই। এছাড়াও, আপনার 'বই_রেটিং' আংশিক প্রয়োজন। এটি আরও সহজে করতে, আপনি render_async রত্নটি ব্যবহার করতে পারেন৷ আপনি যেখানে আপনার বইয়ের রেটিং রেন্ডার করবেন সেখানে আপনাকে নিম্নলিখিত বিবৃতিটি রাখতে হবে:

<%= render_async book_rating_path %>

রেটিংটিকে book_rating-এ রেন্ডার করার জন্য HTML বের করুন আংশিক, এবং রাখুন:

<%= content_for :render_async %>

আপনার লেআউট ফাইলের ভিতরে, রত্নটি book_rating_path কল করবে আপনার পৃষ্ঠা লোড হয়ে গেলে একটি AJAX অনুরোধ সহ, এবং রেটিং আনা হলে, এটি পৃষ্ঠায় দেখাবে৷ এর মধ্যে একটি বড় লাভ হল যে আপনার ব্যবহারকারীরা আলাদাভাবে রেটিং লোড করার মাধ্যমে বইটির পৃষ্ঠা দ্রুত দেখতে পাবেন৷

অথবা, আপনি যদি চান, আপনি বেসক্যাম্প থেকে টার্বো ফ্রেম ব্যবহার করতে পারেন৷ ধারণাটি একই, তবে আপনি কেবল <turbo-frame> ব্যবহার করুন আপনার মার্কআপের উপাদান যেমন:

<turbo-frame id="rating_1" src="/books/1/rating"> </turbo-frame>

আপনি যে বিকল্পটি চয়ন করুন না কেন, ধারণাটি হল আপনার প্রধান নিয়ন্ত্রক ক্রিয়া থেকে ভারী বা অস্পষ্ট কাজকে বিভক্ত করা এবং যত তাড়াতাড়ি সম্ভব ব্যবহারকারীকে পৃষ্ঠাটি দেখান৷

চূড়ান্ত চিন্তা

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

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

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

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


  1. এসএম বাস কন্ট্রোলার এবং এর ড্রাইভার

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

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

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