কম্পিউটার টিউটোরিয়াল

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

আপনি কি কখনো Facebook-এ যেন পেজ রিফ্রেশ না করেও নোটিফিকেশন পেয়েছেন? এই রিয়েল-টাইম ফাংশনালিটি অধিকাংশ অ্যাপ্লিকেশনে JavaScript ফ্রেমওয়ার্ক ব্যবহার করে অর্জিত হয়, যেমন React ভিয়া স্টেট ম্যানেজমেন্ট। এই অধিকাংশ অ্যাপ্লিকেশন シングル-পেজ অ্যাপ্লিকেশন হিসেবে ফাংশন করে, কারণ সেগুলো রিয়েল-টাইমে ডেটা আপডেট করার জন্য পেজ রিলোড দরকার নয়। অনেক দিন ধরে, Rails অ্যাপ্লিকেশনগুলো স্টেটলেস بهذا বোঝা যেতে পারে যে একটি পেজ রিলোড সাধারণত দরকার অ্যাপ্লিকেশনের কারেন্ট স্টেট পেতে। উদাহরণস্বরূপ, আপনি যদি একটি Rails অ্যাপে থাকেন যা থিয়েটারে উপলব্ধ মুভির লিস্ট শো করে এবং একটি অ্যাডমিন একটি নতুন মুভি যোগ করে, তবে নতুন যোগ করা মুভিটি আপনার ড্যাশবোর্ডে শো করবে না যতক্ষণ না আপনি পেজটি রিফ্রেশ করেন।

ActionCable কেন?

ActionCable এই গ্যাপটি পূরণ করে এবং একটি Rails অ্যাপ্লিকেশনে রিয়েল-টাইম আপডেট এবং এই ডাইনামিক ফাংশনালিটি সম্ভব করে তোলে। এটি WebSocket নামক একটি কমিউনিকেশন প্রোটোকল ব্যবহার করে অ্যাপ্লিকেশনে স্টেট আনতে পারবে যেটি এখনও পারফরম্যান্ট এবং স্কেলেবল। এর সাথে, ইউজাররা তাদের পেজ রিফ্রেশ ছাড়া তাদের ড্যাশবোর্ডে আপডেটেড কন্টেন্ট পেতে পারবে।

Turbo-Rails-এর জাদু

TurboRails সম্বলিত turbo drive, turbo frames, এবং turbo streams। যখন একটি রিকোয়েস্ট পাঠানো হয় পেজের একটি অংশ থেকে যা একটি turbo-frame-এ র‍্যাপ করা, HTML রেসপন্স ফ্রেমটি রিপ্লেস করবে যেটি থেকে এটি উৎপন্ন হয়েছে যদি সেগুলোর একই id থাকে। Turbo streams, অন্যদিকে, এই পারশিয়াল পেজ আপডেটকে একটি ওয়েব সকেট কানেকশনের ওপর সক্ষম করে। ActionCable এই আপডেটগুলো একটি চ্যানেল থেকে ব্রডকাস্ট করে, এবং Turbo stream এই চ্যানেলের সাবস্ক্রাইবার তৈরি করে এবং আপডেটগুলো ডেলিভার করে। ফলস্বরূপ, অ্যাসিনক্রোনাস আপডেট সরাসরি মডেল চেঞ্জের রেসপন্সে তৈরি করা যায়।

আমরা কী বিল্ড করার উদ্দেশ্য রাখি

এই আর্টিকেলের উদ্দেশ্য হলো দেখানো Turbo কীভাবে ActionCable-র সাথে পেছনের দরজায় কাজ করে রিয়েল-টাইম আপডেট ব্রডকাস্ট এবং ডিসপ্লে করতে একটি Rails 6 App-এ। অতএব, আমরা একটি চ্যাট অ্যাপ বিল্ড করব যেখানে একটি চ্যাট রুম যেকোনো ইউজার তৈরি করতে পারবে, এবং সব ইউজার সেই রুমে মেসেজ পাঠাতে পারবে এবং রিয়েল-টাইমে আপডেট পাবে। আমরা ইউজাররা একে অপরের সাথে প্রাইভেটলি চ্যাট করতে পারবে সেটিও ইনাবল করব। আমরা গ্রুপ চ্যাট ইনভাইট ইমপ্লিমেন্ট করব না; এগুলো এই ব্লগ পোস্টের স্কোপের বাইরে যেহেতু শুধু এডিশনাল ডেটাবেস ডিজাইন ইনভল্ভ করে Turbo এবং ActionCable-এর বদলে। যাইহোক, এই লেসনের পর, এটি আপনার জন্য কেকের টুকরো হবে আপনি যদি আরও এগিয়ে যাওয়া চান।

এটি কেমন দেখাবে, বা এতে কী থাকবে?

  • একটি ইনডেক্স পেজ যা সব এক্সিস্টিং চ্যাট রুম এবং ইউজার লিস্ট করে।
  • এই পেজটি ডাইনামিক্যালি আপডেট হওয়া উচিত যখন নতুন ইউজার সাইন আপ করে বা নতুন রুম তৈরি হয়।
  • নতুন চ্যাট রুম তৈরি করার একটি ফর্ম উপস্থিত।
  • যেকোনো চ্যাট রুমে থাকা সময় মেসেজ তৈরি করার একটি চ্যাট বক্স।

শুরু করার ৭টি সহজ ধাপ

এই অ্যাপে, আমরা ইউজারদের শুধু তাদের ইউনিক ইউজারনেম ব্যবহার করে লগইন করতে দেব, যা সেশন ব্যবহার করে অর্জিত হয়।

১. একটি নতুন rails অ্যাপ তৈরি করুন

rails new chatapp
cd chatapp

২. একটি ইউজার মডেল তৈরি করুন এবং মাইগ্রেট করুন

rails g model User username
rails db:migrate

তারপর, আমরা ইউজারনেমের জন্য একটি ইউনিক ভ্যালিডেশন যোগ করব যেহেতু আমরা সব ইউজারনেম তাদের মালিকদের জন্য یونিক চাই। আমরা একটি স্কোপও তৈরি করব কারেন্ট ইউজার ছাড়া সব ইউজার ফেচ করতে আমাদের ইউজার লিস্টের জন্য, কারণ আমরা একটি ইউজার নিজের সাথে চ্যাট করতে চাই না :).

#app/models/user.rb
class User < ApplicationRecord
  validates_uniqueness_of :username
  scope :all_except, ->(user) { where.not(id: user) }
end

৩. একটি চ্যাট রুম মডেল তৈরি করুন

একটি চ্যাট রুমের একটি নাম থাকে এবং এটি একটি প্রাইভেট চ্যাট রুম হতে পারে (দুই ইউজারের মাঝে প্রাইভেট চ্যাটের জন্য) বা পাবলিক (সবার জন্য উপলব্ধ)। এটি ইন্ডিকেট করতে, আমরা আমাদের রুম টেবিলে একটি is_private কলাম যোগ করব।

rails g model Room name:string is_private:boolean

এই ফাইল মাইগ্রেট করার আগে, আমরা is_private কলামে একটি ডিফল্ট ভ্যালু যোগ করব যাতে সব তৈরি হওয়া রুম ডিফল্টলি পাবলিক হয়, অন্যथा বলা না হয় পর্যন্ত।

class CreateRooms < ActiveRecord::Migration[6.1]
  def change
    create_table :rooms do |t|
      t.string :name
      t.boolean :is_private, :default => false
      t.timestamps
    end
  end
end

এই ধাপের পর, আমরা আমাদের ফাইল মাইগ্রেট করব rails db:migrate কমান্ড ব্যবহার করে। নাম প্রপার্টির জন্য ইউনিক ভ্যালিডেশন যোগ করা এবং পাবলিক রুম ফেচ করার একটি স্কোপ যোগ করা প্রয়োজন আমাদের রুম লিস্টের জন্য।

#app/models/room.rb
class Room < ApplicationRecord
  validates_uniqueness_of :name
  scope :public_rooms, -> { where(is_private: false) }
end

৪. স্টাইলিং যোগ করুন

এই অ্যাপে মিনিমাল স্টাইলিং যোগ করতে, আমরা Bootstrap CDN যোগ করব আমাদের application.html.erb ফাইলে

<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

৫. অথেনটিকেশন যোগ করুন

অ্যাপে অথেনটিকেশন যোগ করার জন্য একটি current_user ভেরিয়েবল সবসময় দরকার। নিচের কোডগুলো স্টেট করা ফাইলগুলিতে আপনার অ্যাপে যোগ করুন অথেনটিকেশন ইনাবল করতে।

#app/controllers/application_controller.rb
helper_method :current_user
def current_user
  if session[:user_id]
    @current_user = User.find(session[:user_id])
  end
end

def log_in(user)
  session[:user_id] = user.id
  @current_user = user
  redirect_to root_path
end

def logged_in?
  !current_user.nil?
end

def log_out
  session.delete(:user_id)
  @current_user = nil
end
#app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def create
    user = User.find_by(username: params[:session][:username])
    if user
      log_in(user)
    else
      render 'new'
    end
  end

  def destroy
    log_out if logged_in?
    redirect_to root_path
  end
end
#app/views/sessions/new.html.erb
<%= form_for (:session) do |f| %>
  <%= f.label :username, 'Enter your username' %>
  <%= f.text_field :username, autocomplete: 'off' %>
  <%= f.submit 'Sign in' %>
<% end %>

নিচের রুটগুলো routes.rb ফাইলে যোগ করুন।

#routes.rb
Rails.application.routes.draw do
  get '/signin', to: 'sessions#new'
  post '/signin', to: 'sessions#create'
  delete '/signout', to: 'sessions#destroy'
end

৬. কন্ট্রোলার তৈরি করুন

rails g controller Rooms index ব্যবহার করে RoomsController তৈরি করুন এবং আমাদের ইনডেক্স মেথডে ইউজার এবং রুমের লিস্টের ভেরিয়েবল যোগ করুন।

class RoomsController < ApplicationController
  def index
    @current_user = current_user
    redirect_to '/signin' unless @current_user
    @rooms = Room.public_rooms
    @users = User.all_except(@current_user)
  end
end

৭. রুটস সেট আপ করুন

routes.rb-এ রুম, ইউজার এবং রুট রুট যোগ করুন যাতে আমাদের ল্যান্ডিং পেজ ইনডেক্স পেজ হয় যা সব রুম এবং ইউজার লিস্ট করে, এবং আমরা যেকোনো রুমে নেভিগেট করতে পারি।

#routes.rb
resources :rooms
resources :users
root 'rooms#index'

ভিউ সেটআপ করা

Turbo-এর 'জাদু'-র সাথে আমাদের প্রথম পরিচয় হচ্ছে আমাদের ড্যাশবোর্ডে রিয়েল-টাইম আপডেট পাওয়া নতুন অ্যাডড রুম বা নতুন সাইন-আপ ইউজারের ইভেন্টে। এটি অর্জন করতে, প্রথমে, আমরা দুটি পার্শিয়াল তৈরি করব: _room.html.erb প্রতিটি রুম ডিসপ্লে করার জন্য এবং _user.html.erb প্রতিটি ইউজার ডিসপ্লে করার জন্য। আমরা এই লিস্টটি রেন্ডার করব index.html.erb ফাইলে যা তৈরি হয়েছিল RoomsController তৈরি হলে, কারণ এটি আমাদের ল্যান্ডিং পেজ।

# app/views/rooms/_room.html.erb
<div>
  <%= link_to room.name, room %>
</div>
# app/views/users/_user.html.erb
<div>
  <%= link_to user.username, user %>
</div>

আমরা এগিয়ে যাই এই ফাইলগুলো আমাদের index.html.erb ফাইলে রেন্ডার করতে, সরাসরি রেফারেন্স না করে বরং কালেকশন ফেচ করার ভেরিয়েবল রেন্ডার করে। রিকল করুন যে আমাদের RoomsController-এ, ভেরিয়েবল @users এবং @rooms ইতিমধ্যে ডিফাইন করা আছে।

#app/views/rooms/index.html.erb
<div class="container">
  <h5> Hi <%= @current_user.username %> </h5>
  <h4> Users </h4>
  <%= render @users %>
  <h4> Rooms </h4>
  <%= render @rooms %>
</div>

কনসোলে, নিচের কমান্ডগুলো রান করুন:

Room.create(name: 'music')
User.create(username: 'Drake')
User.create(username: 'Elon')

Rails সার্ভার অন করুন rails s ব্যবহার করে। আপনাকে সাইন ইন করতে বলবে; উপরে তৈরি করা যেকোনো ইউজারনার ইউজারনেম দিয়ে সাইন ইন করুন, এবং আপনার নতুন তৈরি রুম এবং সাইন ইন না করা ইউজার শো করবে নিচের ইমেজের মত।

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

Turbo পরিচিতি

রিয়েল-টাইম আপডেট অর্জন করতে, আমাদের Turbo ইনস্টল থাকতে হবে।

bundle add turbo-rails
rails turbo:install

নিচের কমান্ডগুলো রান করুন যদি আপনার Redis ইনস্টল না থাকে:

sudo apt install redis-server #redis ইনস্টল করুন যদি এখনো নেই
redis-server #সার্ভার শুরু করুন

import "@hotwired/turbo-rails" ব্যবহার করে turbo-rails ইম্পোর্ট করুন application.js ফাইলে।

এরপর, আমরা আমাদের মডেলগুলোতে স্পেসিফিক ইনস্ট্রাকশন যোগ করব এবং সেগুলোকে বলব যেকোনো নতুন ইনস্ট্যান্স অ্যাড হলে একটি নির্দিষ্ট চ্যানেলে ব্রডকাস্ট করতে। এই ব্রডকাস্ট ActionCable দ্বারা করা হয়, як আমরা শীঘ্রই দেখব।

#app/models/user.rb
class User < ApplicationRecord
  validates_uniqueness_of :username
  scope :all_except, ->(user) { where.not(id: user) }
  after_create_commit { broadcast_append_to "users" }
end

এখানে, আমরা ইউজার মডেলকে বলছি একটি নতুন ইউজার ইনস্ট্যান্স তৈরি হওয়ার পর "users" নামে একটি চ্যানেলে ব্রডকাস্ট করতে।

#app/models/room.rb
class Room < ApplicationRecord
  validates_uniqueness_of :name
  scope :public_rooms, -> { where(is_private: false) }
  after_create_commit {broadcast_append_to "rooms"}
end

এখানেও, আমরা রুম মডেলকে বলছি প্রতিটি নতুন রুম ইনস্ট্যান্স তৈরি হওয়ার পর "rooms" চ্যানেলে ব্রডকাস্ট করতে।

আপনার কনসোল শুরু করুন যদি এটি শুরু করা না থাকে, অথবা reload! কমান্ড ব্যবহার করুন যদি এটি ইতিমধ্যে চলছে। একটি নতুন ইনস্ট্যান্স তৈরি করার পর যেকোনো একটির, আমরা দেখি যে ActionCable অ্যাড করা ইনস্ট্যান্সটি স্পেসিফাইড চ্যানেলে একটি turbo stream হিসেবে ব্রডকাস্ট করে পার্শিয়ালটি টেমপ্লেট হিসেবে ব্যবহার করে যা সেটির জন্য অ্যাসাইন করা। একটি নতুন অ্যাডড রুমের জন্য, এটি পার্শিয়াল _room.html.erb ব্রডকাস্ট করে নতুন অ্যাডড ইনস্ট্যান্সের সাথে মিলে যাওয়া ভ্যালুসহ, নিচে দেখানো মত।

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

সমস্যা হলো, ব্রডকাস্ট করা টেমপ্লেট ড্যাশবোর্ডে শো হয় না। এটি কারণ আমাদের ভিউতে ব্রডকাস্টের একটি রিসিভার যোগ করতে হবে যাতে ActionCable দ্বারা যা ব্রডকাস্ট হচ্ছে সেটি রিসিভ করা যায় এবং অ্যাপেন্ড করা যায়। আমরা এটি করব একটি turbo_stream_from ট্যাগ যোগ করে, চ্যানেলটি স্পেসিফাই করে যেখান থেকে আমরা ব্রডকাস্ট রিসিভ করতে আশা করি। উপরের ইমেজে দেখানো মত, ব্রডকাস্ট করা স্ট্রিমে একটি টার্গেট অ্যাট্রিবিউট আছে, এবং এটি স্পেসিফাই করে যে কন্টেইনারটির আইডি যেখানে স্ট্রিমটি অ্যাপেন্ড হবে। এর মানে ব্রডকাস্ট করা টেমপ্লেটটি একটি কন্টেইনার খুঁজবে যার আইডি "rooms" যাতে সেটি সেখানে অ্যাপেন্ড হয়; অতএব, আমরা আমাদের ইনডেক্স ফাইলে সে আইডি সহ একটি div ইনক্লুড করি। এটি অর্জন করতে, আমাদের index.html.erb ফাইলে, আমরা <%= render @users %> রিপ্লেস করি:

<%= turbo_stream_from "users" %>
<div id="users">
  <%= render @users %>
</div>

এবং <%= render @rooms %>-কে

<%= turbo_stream_from "rooms" %>
<div id="rooms">
  <%= render @rooms %>
</div>

এই মুহূর্তে, আমরা Turbo-এর জাদু অনুভব করতে পারি। আমরা আমাদের পেজ রিফ্রেশ করতে পারি এবং কনসোল থেকে নতুন ইউজার এবং রুম যোগ করা শুরু করতে পারি এবং সেগুলো রিয়েল-টাইমে আমাদের পেজে অ্যাপেন্ড होते দেখতে পারি। Yippee!!!

কনসোল থেকে নতুন রুম তৈরি করতে ক্লান্ত? চলুন একটি ফর্ম যোগ করি যা ইউজারদের নতুন রুম তৈরি করতে দেবে।

#app/views/layouts/_new_room_form.html.erb
<%= form_with(model: @room, remote: true, class: "d-flex" ) do |f| %>
  <%= f.text_field :name, class: "form-control", autocomplete: 'off' %>
  <%= f.submit data: { disable_with: false } %>
<% end %>

উপরোক্ত ফর্মে, @room ব্যবহার করা হয়েছে, কিন্তু এটি এখনো আমাদের কন্ট্রোলারে ডিফাইন করা নেই; অতএব, আমরা এটিকে ডিফাইন করি এবং আমাদের RoomsController-এর ইনডেক্স মেথডে যোগ করি।

@room = Room.new

যখন ক্রিয়েট বাটনে ক্লিক করা হবে, এটি RoomsController-এ একটি create মেথডে রাউট করবে, যা এই মুহূর্তে অস্তিত্বে নেই; অতএব, আমাদের এটিকে যোগ করতে হবে।

#app/controllers/rooms_controller.rb
def create
  @room = Room.create(name: params["room"]["name"])
end

আমরা এই ফর্মটি আমাদের ইনডেক্স ফাইলে এর পার্শিয়াল রেন্ডার করে যোগ করতে পারি:

<%= render partial: "layouts/new_room_form" %>

এছাড়া, আমরা কিছু বুটস্ট্রাপ ক্লাস যোগ করতে পারি পেজকে রুম এবং ইউজার লিস্টের জন্য একটি অংশ এবং চ্যাটের জন্য অন্য অংশে ভাগ করতে।

<div class="row">
  <div class="col-md-2">
    <h5> Hi <%= @current_user.username %> </h5>
    <h4> Users </h4>
    <div>
      <%= turbo_stream_from "users" %>
      <div id="users">
        <%= render @users %>
      </div>
    </div>
    <h4> Rooms </h4>
    <%= render partial: "layouts/new_room_form" %>
    <div>
      <%= turbo_stream_from "rooms" %>
      <div id="rooms">
        <%= render @rooms %>
      </div>
    </div>
  </div>
  <div class="col-md-10 bg-dark">
    The chat box stays here
  </div>
</div>

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড রিয়েল-টাইমে আপডেট হওয়া অ্যাডড রুমের ইমেজ

এখন, নতুন রুম তৈরি করলে, আমরা দেখি যে এগুলো তৈরি হচ্ছে, এবং পেজটি রিয়েল-টাইমে আপডেট হচ্ছে। আপনি সম্ভবত লক্ষ্য করেছেন যে ফর্মটি প্রতিটি সাবমিশনের পর ক্লিয়ার হচ্ছে না; আমরা এই সমস্যাটি পরে Stimulus ব্যবহার করে হ্যান্ডেল করব।

গ্রুপ চ্যাট

গ্রুপ চ্যাটের জন্য, আমাদেরকে إندিভিজুয়াল রুমে রাউট করতে হবে কিন্তু একই পেজে থাকতে হবে। আমরা এটি করব ইনডেক্স পেজের জন্য দরকার সব ভেরিয়েবল আমাদের RoomsController show মেথডে যোগ করে এবং ইনডেক্স পেজটি রেন্ডার করে।

#app/controllers/rooms_controller.rb
def show
  @current_user = current_user
  @single_room = Room.find(params[:id])
  @rooms = Room.public_rooms
  @users = User.all_except(@current_user)
  @room = Room.new
  render "index"
end

এক্সট্রা একটি ভেরিয়েবল নামক @single_room show মেথডে যোগ করা হয়েছে। এটি আমাদের সেই নির্দিষ্ট রুমটি দেয় যেটিতে আমরা নেভিগেট করছি; অতএব, আমরা আমাদের ইনডেক্স পেজে একটি কন্ডিশনাল স্টেটমেন্ট যোগ করতে পারি যা রুমের নাম শো করবে যখন একটি রুমের নাম ক্লিক করা হয়। এটি div-এ যোগ করা হয়েছে যার ক্লাস নাম col-md-10, নিচে দেখানো মত।

<div class="col-md-10 bg-dark text-light">
  <% if @single_room %>
    <h4 class="text-center">
      <%= @single_room.name %>
    </h4>
  <% end %>
</div>

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড কई রুমে নেভিগেট করার ইমেজ

এখন আমরা কিছু আরও জুসি জিনিসে যাব, মেসেজিং। আমাদের চ্যাট সেকশনকে ১০০vh হাইট দিতে হবে যাতে এটি পেজটি পূরণ করে এবং এতে একটি চ্যাট বক্স ইনক্লুড করব মেসেজ তৈরির জন্য। চ্যাটবক্সের একটি মেসেজ মডেল দরকার। এই মডেলের একটি ইউজার রেফারেন্স এবং একটি রুম রেফারেন্স থাকবে, কারণ একটি মেসেজ তার ক্রিয়েটর এবং যে রুমের জন্য এটি ছিল সেটি ছাড়া অস্তিত্বে থাকতে পারে না।

rails g model Message user:references room:references content:text
rails db:migrate

আমাদের ইউজার এবং রুম মডেলে এই অ্যাসোসিয়েশন আইডেন্টিফাই করতে হবে নিচের লাইন যোগ করে।

has_many :messages

আমাদের পেজে মেসেজ তৈরির জন্য একটি ফর্ম যোগ করি এবং এটিকে স্টাইল করি:

#app/views/layouts/_new_message_form.html.erb
<div class="form-group msg-form">
  <%= form_with(model: [@single_room ,@message], remote: true, class: "d-flex" ) do |f| %>
    <%= f.text_field :content, id: 'chat-text', class: "form-control msg-content", autocomplete: 'off' %>
    <%= f.submit data: { disable_with: false }, class: "btn btn-primary" %>
  <% end %>
</div>
#app/assets/stylesheets/rooms.scss
.msg-form {
  position: fixed;
  bottom: 0;
  width: 90%
}
.col-md-10 {
  height: 100vh;
  overflow: scroll;
}
.msg-content {
  width: 80%;
  margin-right: 5px;
}

এই ফর্মে একটি @message ভেরিয়েবল অন্তর্ভুক্ত; অতএব, আমাদের কন্ট্রোলারে এটিকে ডিফাইন করতে হবে। আমরা আমাদের RoomsController-এর show মেথডে এটি যোগ করি।

@message = Message.new

আমাদের routes.rb ফাইলে, আমরা মেসেজ রিসোর্সটি রুম রিসোর্সের ভিতরে যোগ করি, কারণ এটি params-এ রুমের আইডি আট্যাচ করে যেখান থেকে মেসেজটি তৈরি হচ্ছে।

resources :rooms do
  resources :messages
end

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

আমরা আমাদের মেসেজ মডেলে এটি যোগ করি:

#app/models/message.rb
after_create_commit { broadcast_append_to self.room }

এইভাবে, এটি সেটি যেখানে তৈরি হয়েছে সেই নির্দিষ্ট রুমে ব্রডকাস্ট করে।

আমরা স্ট্রিম, মেসেজ কন্টেইনার, এবং মেসেজ ফর্ম আমাদের ইনডেক্স ফাইলে যোগ করি:

# @single_room কন্ডিশনের ভিতরে app/views/rooms/index.html.erb-এ
<%= turbo_stream_from @single_room %>
<div id="messages">
</div>
<%= render partial: 'layouts/new_message_form' >

আমরা মেসেজ পার্শিয়াল তৈরি করি যা ব্রডকাস্ট হবে, এবং এতে, আমরা সেন্ডারের ইউজারনেম শো করি শুধু যদি রুমটি পাবলিক হয়।

#app/views/messages/_message.html.erb
<div>
  <% unless message.room.is_private %>
    <h6 class="name">
      <%= message.user.username %>
    </h6>
  <% end %>
  <%= message.content %>
</div>

কনসোল থেকে, যদি আমরা একটি মেসেজ তৈরি করি, আমরা দেখি যে এটি তার অ্যাসাইন করা টেমপ্লেট ব্যবহার করে তার রুমে ব্রডকাস্ট হচ্ছে।

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

ড্যাশবোর্ড থেকে মেসেজ তৈরর ইনাবল করতে, আমাদের MessagesController-এ create মেথড যোগ করতে হবে।

#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def create
    @current_user = current_user
    @message = @current_user.messages.create(content: msg_params[:content], room_id: params[:room_id])
  end

  private

  def msg_params
    params.require(:message).permit(:content)
  end
end

এটি হলো ফলাফল: ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

যেমন আমরা উপরের ভিডিওতে দেখতে পাচ্ছি, মেসেজগুলো অ্যাপেন্ড হচ্ছে, কিন্তু যদি আমরা অন্য একটি চ্যাট রুমে যাই, ऐसा লাগছে যে আমরা আগের রুমের মেসেজগুলো হারিয়ে ফেলছি যখন ফিরে আসি। এটি কারণ আমরা রুমের মেসেজগুলো ফেচ করছি না ডিসপ্লে করার জন্য। এটি করার জন্য, RoomsController show মেথডে, আমরা একটি ভেরিয়েবল যোগ করি যা একটি রুমের সব মেসেজ ফেচ করে, এবং ইনডেক্স পেজে, আমরা ফেচ করা মেসেজগুলো রেন্ডার করি। আমরা দেখতে পাচ্ছি যে মেসেজ ফর্মটি একটি মেসেজ পাঠানোর পর ক্লিয়ার হয় না; এটি Stimulus দিয়ে নিচে হ্যান্ডেল করা হবে।

# app/controllers/rooms_controller.rb-এ show মেথডে
@messages = @single_room.messages
# 'messages' আইডি वाले div-এ
<%= render @messages %>

এখন, প্রতিটি রুম তার মেসেজ লোডেড থাকবে এন্ট্রি করার সাথে সাথে।

এটি আরও প্রেজেন্টেবল করার জন্য, কারেন্ট ইউজারের মেসেজগুলো ডানে আলাইন করি এবং অন্যদের বামে। এটি অর্জন করার সবচেয়ে স্ট্রেইটফরওয়ার্ড উপায় হচ্ছে message.user == current_user কন্ডিশন বেজড ক্লাস অ্যাসাইন করা, কিন্তু লোকাল ভেরিয়েবলস স্ট্রিমসকে উপলব্ধ নয়; অতএব, একটি ব্রডকাস্ট করা মেসেজের জন্য, কোনো current_user থাকবে না। আমরা কী করতে পারি? আমরা মেসেজ কন্টেইনারে একটি ক্লাস অ্যাসাইন করতে পারি মেসেজ সেন্ডারের আইডি বেজড এবং তারপর current_user হেল্পার মেথডর সুবিধা নিতে পারি আমাদের application.html.erb ফাইলে একটি স্টাইল যোগ করতে। এইভাবে, যদি কারেন্ট ইউজারের আইডি ২ হয়, স্টাইল ট্যাগে application.html.erb-এ ক্লাস হবে .msg-2, যা আমাদের মেসেজ পার্শিয়ালের ক্লাসের সাথে মিলবে যখন মেসেজ সেন্ডার কারেন্ট ইউজার।

#app/views/messages/_message.html.erb
<div class="cont-<%= message.user.id %>">
  <div class="message-box msg-<%= message.user.id %> ">
    <% unless message.room.is_private %>
      <h6 class="name">
        <%= message.user.username %>
      </h6>
    <% end %>
    <%= message.content %>
  </div>
</div>

আমরা message-box স্টাইলিং যোগ করি:

#app/assets/stylesheets/rooms.scss
.message-box {
  width: fit-content;
  max-width: 40%;
  padding: 5px;
  border-radius: 10px;
  margin-bottom: 10px;
  background-color: #555555 ;
  padding: 10px
}

আমাদের application.html.erb ফাইলের হেড ট্যাগে।

#app/views/layouts/application.html.erb
<style>
  <%= ".msg-#{current_user&.id}" %> { background-color: #007bff !important; padding: 10px; }
  <%= ".cont-#{current_user&.id}" %> { display: flex; justify-content: flex-end }
</style>

আমরা !important ট্যাগ যোগ করি background-color-এ কারণ আমরা ব্যাকগ্রাউন্ড কলার ওভাররাইড করতে চাই কারেন্ট ইউজারের জন্য।

আমাদের চ্যাটগুলো এখন এরকম দেখে: ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

প্রাইভেট চ্যাট

প্রাইভেট চ্যাটের জন্য দরকার অধিকাংশ কাজ গ্রুপ চ্যাট সেটআপের সময় হয়েছে। এখন আমাদের শুধু নিচের مواردগুলো করতে হবে:

  • একটি প্রাইভেট রুম তৈরি করুন একটি প্রাইভেট চ্যাটের জন্য যখন একটি নির্দিষ্ট ইউজারর রাউট করা হয় যদি এমন রুম নন-এক্সিস্টেন্ট হয়।
  • সায়/rooms-এ পার্টিসিপ্যান্ট তৈরি করুন যাতে ইন্ট্রুডার মেসেজ পাঠাতে না পারে এমন রুমে, এমনকি কনসোল থেকেও।
  • নতুন তৈরি প্রাইভেট রুমগুলো রুম লিস্টে ব্রডকাস্ট হচ্ছে না সেটি প্রিভেন্ট করুন।
  • যখন এটি একটি প্রাইভেট চ্যাট, রুমের নামের বদলে ইউজারর নাম ডিসপ্লে করুন।

এক নির্দিষ্ট ইউজারর রাউট করার সময়, কারেন্ট ইউজার ইন্ডিকেট করছে যে সে সে ইউজারের সাথে প্রাইভেটলি চ্যাট করতে চায়। অতএব, আমাদের UsersController-এ, আমরা চেক করি 그러한 একটি প্রাইভেট রুম এক্সিস্ট করে কিনা এই দুজনের মাঝে। যদি করে, সেটি আমাদের @single_room ভেরিয়েবল হয়ে যায়; অন্যথায়, আমরা সেটি তৈরি করব। আমরা প্রতিটি প্রাইভেট চ্যাট রুমের জন্য একটি স্পেশাল রুম নাম তৈরি করব যাতে আমরা দরকার पड़লে সেটি রেফারেন্স করতে পারি। আমাদের ইনডেক্স পেজের জন্য দরকার সব ভেরিয়েবল ইনক্লুড করতে হবে show মেথডে একই পেজে থাকতে।

#app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @current_user = current_user
    @rooms = Room.public_rooms
    @users = User.all_except(@current_user)
    @room = Room.new
    @message = Message.new
    @room_name = get_name(@user, @current_user)
    @single_room = Room.where(name: @room_name).first || Room.create_private_room([@user, @current_user], @room_name)
    @messages = @single_room.messages
    render "rooms/index"
  end

  private

  def get_name(user1, user2)
    users = [user1, user2].sort
    "private_#{users[0].id}_#{users[1].id}"
  end
end

উপর দেখানো মত, আমাদের Room মডেলে একটি create_private_room মেথড যোগ করতে হবে, যা একটি প্রাইভেট রুম এবং এর পার্টিসিপ্যান্ট তৈরি করবে। এটি আমাদেরকে একটি নতুন মডেল Participant তৈরি করতে বাধ্য করে, যা একটি ইউজার এবং সে প্রাইভেট রুমটি যেখানে সে BELONGS ইন্ডিকেট করবে।

rails g model Participant user:references room:references
rails db:migrate

আমাদের রুম মডেলে, আমরা create_private_room মেথড যোগ করি এবং আমাদের after_create কলকে পরিবর্তন করি শুধু রুমের নাম ব্রডকাস্ট করতে যদি এটি প্রাইভেট না হয়।

#app/models/room.rb
has_many :participants, dependent: :destroy
after_create_commit { broadcast_if_public }

def broadcast_if_public
  broadcast_append_to "rooms" unless self.is_private
end

def self.create_private_room(users, room_name)
  single_room = Room.create(name: room_name, is_private: true)
  users.each do |user|
    Participant.create(user_id: user.id, room_id: single_room.id )
  end
  single_room
end

অন্য ইউজাররা একটি প্রাইভেট রুমে মেসেজ পাঠাতে না পারে যখন সে পার্টিসিপ্যান্ট নয়, এটি প্রিভেন্ট করতে, আমরা Message মডেলে একটি before_create চেক যোগ করি। এইভাবে, প্রাইভেট রুমের জন্য, একটি কনফার্মেশন করা হয় যে মেসেজের সেন্ডার সেটি প্রাইভেট কনভারসেশনের একজন পার্টিসিপ্যান্ট কিনা এমন মেসেজ তৈরি করার আগে। আমাদের নোট করা উচিত যে ড্যাশবোর্ড থেকে, একটি প্রাইভেট রুমে মেসেজ পাঠানো অসম্ভব যদি আপনি পার্টিসিপ্যান্ট না হন যেহেতু আপনাকে শুধু ইউজারনেমে ক্লিক করতে হবে এবং রুমটি দুজনের জন্য তৈরি হয়ে যাবে। এই চেকটি শুধু এক্সট্রা সিকিউরিটির জন্য, কারণ একটি মেসেজ কনসোল থেকে একটি নন-পার্টিসিপ্যান্ট দ্বারা তৈরি করা যেতে পারে।

#app/models/message.rb
before_create :confirm_participant

def confirm_participant
  if self.room.is_private
    is_participant = Participant.where(user_id: self.user.id, room_id: self.room.id).first
    throw :abort unless is_participant
  end
end

প্রাইভেট চ্যাটের সময় রুমের নামের বদলে ইউজারর ইউজারনেম ডিসপ্লে করতে, আমরা আমাদের ইনডেক্স পেজে ইন্ডিকেট করি যে যদি @user ভেরিয়েবল প্রেজেন্ট থাকে, ইউজারর ইউজারনেম শো করা উচিত। নোট করুন যে এই ভেরিয়েবলটি শুধু UsersController show মেথডে প্রেজেন্ট থাকে। এর ফলে রুমের নাম শো করার h4 ট্যাগটি এতদিনে এরকম হয়ে যায়:

<h4 class="text-center">
  <%= @user&.username || @single_room.name %>
</h4>

এখন, যখন আমরা একটি ইউজারর নেভিগেট করি, আমরা রুমের নামের বদলে ইউজারর ইউজারনেম দেখি, এবং আমরা মেসেজ পাঠাতে এবং পাওয়া যায়। সাইন-আউট লিংক আমাদের হোম পেজে যোগ করা মনে রাখবেন।

<%= link_to 'Sign Out', signout_path, :method => :delete %>

নিচে দেখানো মত, প্রাইভেট চ্যাটের জন্য, সেন্ডারের ইউজারনেম শো করা হয় না। আমরা আমাদের নিজের মেসেজগুলো তাদের পজিশন দিয়ে আইডেন্টিফাই করতে পারি।

ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

Stimulus

আমরা Stimulus ব্যবহার করব ফর্মস ক্লিয়ার করতে যেহেতু একটি ফুল-পেজ রি-রেন্ডার হয় না, এবং ফর্মগুলো একটি নতুন মডেল ইনস্ট্যান্স তৈরির ওপর ক্লিয়ার হয় না।

bundle add stimulus-rails
rails stimulus:install

এটি নিচের ফাইলগুলো অ্যাড করে, নিচের ইমেজে দেখানো মত। ActionCable এবং Turbo দিয়ে Rails-এ লাইভ চ্যাট অ্যাপ বিল্ড করুন: ধাপে ধাপে গাইড

আমরা একটি reset_form_controller.js ফাইল তৈরি করি আমাদের ফর্ম রিসেট করতে এবং নিচের ফাংশনটি যোগ করি।

//app/javascript/controllers/reset_form_controller.js
import { Controller } from "stimulus"
export default class extends Controller {
  reset() {
    this.element.reset()
  }
}

তারপর, আমরা আমাদের ফর্মগুলিতে একটি ডেটা অ্যাট্রিবিউট যোগ করি, কন্ট্রোলার এবং অ্যাকশন ইন্ডিকেট করে।

data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" }

উদাহরণস্বরূপ, আমাদের মেসেজ ফর্মের form_with ট্যাগটি নিচের মত পরিবর্তন হয়:

<%= form_with(model: [@single_room ,@message], remote: true, class: "d-flex", data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" }) do |f| %>

শেষে, এটাই সব যা দরকার; আমাদের ফর্ম ক্লিয়ার হয়ে যায় একটি নতুন মেসেজ বা রুম তৈরি হওয়ার পর। আমাদের নোট করা উচিত যে Stimulus অ্যাকশন "ajax:success->reset-form#reset"ও একটি ফর্ম ক্লিয়ার করতে পারে যখন একটি ajax:success ইভেন্ট ঘটে।

উপসংহার

এই অ্যাপে, আমরা Turbo Streams-এর অ্যাপেন্ড অ্যাকশনের ওপর ফোকাস করেছি, কিন্তু এটি সকল নয় Turbo Streams-এর। আসলে, Turbo Streams পাঁচটি অ্যাকশন সম্বলিত: append, prepend, replace, update, এবং remove। চ্যাট মেসেজ রিয়েল-টাইমে ডিলিট এবং আপডেট ইমপ্লিমেন্ট করতে, এই অ্যাকশনগুলো কাজে আসবে, এবং Turbo frames কীভাবে কাজ করে সেটি সম্পর্কে কিছু জ্ঞান দরকার হতে পারে। এটি নোট করা গুরুত্বপূর্ণ যে যেসব অ্যাপ্লিকেশন WebSocket আপডেটের ওপর নির্দিষ্ট ফিচার্সের জন্য ডিপেন্ড করে, খারাপ কানেকশন বা সার্ভার ইস্যু থাকলে, আপনার WebSocket ডিসকানেক্ট হতে পারে। তাই Turbo Streams আপনার অ্যাপে শুধু তখন ব্যবহার করা উপযুক্ত যখন এটি অত্যন্ত গুরুত্বপূর্ণ।

  1. Vue, Vuex, এবং Rails দিয়ে ফুল-স্ট্যাক ডেভেলপমেন্ট মাস্টার করুন: একটি স্কেলেবল পদ্ধতি

  2. রেইলস প্রজেক্টের জন্য দক্ষ ডকুমেন্টেশন ওয়ার্কফ্লো ডিজাইন

  3. রিঅ্যাক্ট অন রেইলস: সিমলেস ফুল-স্ট্যাক অ্যাপ গড়ে তোলা

  4. Angular এবং Rails 5 ইন্টিগ্রেশন: একটি স্মুথ ফুল-স্ট্যাক পদ্ধতি