Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

เคยอยู่บน Facebook ได้ notification โดยไม่ต้อง refresh หน้า? Real-time functionality แบบนี้ทำได้ในแอปส่วนใหญ่ด้วย JavaScript frameworks เช่น React ผ่าน state management แอปส่วนใหญ่ทำงานเป็น single-page applications เพราะไม่ต้อง reload หน้าเพื่อ update data real-time นานแล้ว Rails applications เป็น stateless ในความหมายว่า page reload มักจำเป็นเพื่อได้ current state ของแอป ตัวอย่าง หากอยู่บน Rails app แสดงรายการหนังในโรงหนัง และ admin เพิ่มหนังใหม่ หนังใหม่จะไม่แสดงบน dashboard เว้นแต่ refresh หน้า

ทำไมต้อง ActionCable?

ActionCable บริดจ์ช่องว่างนี้ ทำให้ real-time updates และ dynamic functionality ใน Rails app เป็นไปได้ ใช้ communication protocol ชื่อ WebSocket เพื่อ introduce state เข้าแอปพลิเคชัน โดยยังคง performant และ scalable ด้วยมัน users ได้ updated content บน dashboard โดยไม่ต้อง refresh หน้า

The Magic Of Turbo-Rails

TurboRails ประกอบด้วย turbo drive, turbo frames, และ turbo streams เมื่อ request ส่งจากส่วนของหน้าที่ wrapped ใน turbo-frame HTML response จะแทนที่ frame ที่มันมาจาก หากมี id เดียวกัน Turbo streams เปิดใช้ partial page updates ผ่าน WebSocket connection ActionCable broadcast updates จาก channel และ Turbo stream สร้าง subscriber ไปยัง channel นี้และส่ง updates ผลคือ asynchronous updates สร้างได้โดยตรงตอบสนอง model changes

สิ่งที่เราจะสร้าง

จุดประสงค์คือ showcase ว่า Turbo ทำงานกับ ActionCable ไปพร้อมกัน broadcast และ display real-time updates ใน Rails 6 App เราจะสร้าง chat app ที่ chat room สร้างได้โดย user ใดๆ และทุก users ส่งข้อความไปยัง room นั้นและรับ updates real-time เราจะเปิดให้ users chat 私下 กับกันได้ด้วย เราจะไม่ implement group chat invites เหล่านี้อยู่นอกขอบเขตเพราะเกี่ยวกับ database designs เพิ่มเติม ไม่ใช่ Turbo และ ActionCable แต่หลังบทเรียนนี้ควรทำได้ง่ายถ้าอยากไปต่อ

ควรหน้าตายังไง หรือควรมีอะไรบ้าง?

  • Index page แสดง chat rooms และ users ทั้งหมด
  • หน้านี้ควร dynamically updated เมื่อ users ใหม่สมัครหรือ rooms ใหม่สร้าง
  • Form สำหรับสร้าง chat rooms ใหม่
  • Message chat box สำหรับสร้างข้อความเมื่ออยู่ใน chat room ใดๆ

7 ขั้นตอนง่ายๆ เริ่มต้น

ใน app นี้ เราต้องการ users login ด้วย unique usernames ผ่าน sessions

1. สร้าง Rails app ใหม่

rails new chatapp
cd chatapp

2. สร้าง User model และ migrate

rails g model User username
rails db:migrate

เพิ่ม unique validation สำหรับ username เนื่องจากต้องการ usernames unique ทุกคน สร้าง scope ดึง users ทุกคนยกเว้น current user สำหรับ user list เนื่องจากไม่ต้องการ user chat กับตัวเอง :)

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

3. สร้าง Chat Room model

Chat room มี name และอาจเป็น private chat room (สำหรับ private chats ระหว่าง 2 users) หรือ public (ทุกคนเข้าถึงได้) เพื่อระบุเพิ่ม column is_private ใน room table

rails g model Room name:string is_private:boolean

ก่อน migrate ไฟล์นี้ เราจะเพิ่ม default value ให้ is_private column ทำให้ rooms ที่สร้างเป็น public by default เว้นแต่ระบุเป็นอย่างอื่น

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

หลังขั้นตอนนี้ migrate ด้วย rails db:migrate จำเป็นต้องเพิ่ม uniqueness validation สำหรับ name property และ scope ดึง public rooms ทั้งหมดสำหรับ room list

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

4. เพิ่ม Styling

เพื่อ minimal styling เพิ่ม 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">

5. เพิ่ม Authentication

เพิ่ม authentication ต้องการ current_user variable ตลอดเวลา เพิ่มโค้ดต่อไปในไฟล์ที่ระบุเพื่อ enable auth

# 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 ต่อไปใน 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

6. สร้าง Controller

สร้าง RoomsController ด้วย rails g controller Rooms index และเพิ่ม variables สำหรับ list ของ users และ rooms ใน index method

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

7. ตั้งค่า Routes

เพิ่ม room, user และ root routes ใน routes.rb ทำให้ landing page เป็น index page แสดง rooms และ users และ navigate ไป room ใดๆ ได้

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

ตั้งค่า Views

การแนะนำ 'magic' ของ Turbo ครั้งแรกคือการรับ real-time updates บน dashboard เมื่อมี rooms ใหม่หรือ users ใหม่สมัคร ทำได้โดยสร้าง partials 2 ตัว: _room.html.erb แสดงแต่ละ room และ _user.html.erb แสดงแต่ละ user เราจะ render list นี้ใน index.html.erb ที่สร้างเมื่อสร้าง RoomsController เนื่องจากนี่คือ landing page

# 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>

Render ไฟล์เหล่านี้ใน index.html.erb ไม่ได้โดย reference โดยตรง แต่โดย render variables ที่ fetch collection จำได้ว่าใน RoomsController variables @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>

ใน console รันคำสั่ง:

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

เปิด Rails server ด้วย rails s จะ prompt sign in ลอง sign in ด้วย username ของ users ที่สร้างข้างบน ควรเห็น room ใหม่และ user ที่ไม่ได้ sign in แสดงตามภาพ

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

แนะนำ Turbo

เพื่อ real-time updates ต้องมี Turbo ติดตั้ง

bundle add turbo-rails
rails turbo:install

รันคำสั่งถ้ายังไม่มี Redis ติดตั้ง:

sudo apt install redis-server #installs redis if you don't have it yet
redis-server #starts the server

Import turbo-rails เข้า application.js ด้วย import "@hotwired/turbo-rails"

ต่อไป เพิ่ม instructions ให้ models broadcast instance ใหม่ไป channel เฉพาะ Broadcast นี้ทำโดย 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

ขอให้ user model broadcast ไป channel "users" หลังสร้าง instance ใหม่ทุกครั้ง

# 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

ขอให้ room model broadcast ไป channel "rooms" หลังสร้าง instance ใหม่ทุกครั้ง

เริ่ม console ใหม่หรือ用 reload! หากรันอยู่แล้ว หลังสร้าง instance ใหม่ของทั้งสอง จะเห็น ActionCable broadcast instance ที่เพิ่มไป channel ที่ระบุเป็น turbo stream โดยใช้ partial ที่ assigned เป็น template สำหรับ room ใหม่ broadcast partial _room.html.erb พร้อม values ตรงกับ instance ใหม่

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

ปัญหาคือ broadcasted template ไม่แสดงบน dashboard เพราะต้องเพิ่ม receiver ของ broadcast ใน view เพื่อให้รับและ append ทำได้โดยเพิ่ม tag turbo_stream_from ระบุ channel ที่หวังจะรับ broadcast จากภาพข้างบน broadcasted stream มี target attribute ระบุ id ของ container ที่ stream จะ appended หมายถึง broadcasted template จะค้นหา container id "rooms" เพื่อ append ดังนั้น include div ด้วย id นั้นใน index file ทำได้โดยใน 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>

ตอนนี้ได้ experience magic ของ Turbo แล้ว Refresh หน้าและเริ่มเพิ่ม users และ rooms ใหม่จาก console ดูพวกมัน append ไปหน้า real-time Yippee!!!

เบื่อสร้าง rooms ใหม่จาก console? เพิ่ม form ให้ users สร้าง rooms ใหม่

# 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 %>

Form ใช้ @room แต่ยังไม่ได้ define ใน controller ดังนั้น define และเพิ่มใน index method ของ RoomsController

@room = Room.new

เมื่อกด create button จะ route ไป create method ใน RoomsController ที่ยังไม่มี ต้องเพิ่ม

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

เพิ่ม form ใน index file โดย render partial:

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

เพิ่ม bootstrap classes แบ่งหน้าเป็นส่วน rooms/users list และส่วน chat

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

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน ภาพ rooms ที่เพิ่ม real-time

ตอนนี้สร้าง rooms ใหม่ จะเห็น rooms สร้างและหน้าอัปเดต real-time อาจสังเกต form ไม่ clear หลัง submit จะจัดการภายหลังด้วย stimulus

Group Chats

สำหรับ group chats ต้อง route ไป individual rooms แต่อยู่หน้าเดียวกัน ทำได้โดยเพิ่ม index page variables ทั้งหมดใน RoomsController show method และ render index page เหมือนเดิม

# 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 ให้ room ที่ route ไป ดังนั้นเพิ่ม conditional statement ใน index page แสดงชื่อ room เมื่อคลิกชื่อ room เพิ่มใน div class 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>

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน ภาพแสดงการเข้า room ต่างๆ

ต่อไป messaging ต้องให้ chat section height 100vh เติมหน้า และ include chat box สร้างข้อความ Chatbox ต้องการ Message model มี user reference และ room reference เพราะ message ไม่มี creator และ room ที่หมายถึง

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

ต้องระบุ association ใน user และ room models โดยเพิ่มบรรทัด:

has_many :messages

เพิ่ม form สร้างข้อความและ styling:

# 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; }

Form รวม @message variable ต้อง define ใน controller เพิ่มใน show method ของ RoomsController

@message = Message.new

ใน routes.rb เพิ่ม message resource ใน room resource เพราะ attach params id ของ room ที่สร้าง message

resources :rooms do
  resources :messages
end

เมื่อ message ใหม่สร้าง ต้อง broadcast ไป room ที่สร้าง ต้องการ message partial render message เนื่องจากนี่คือสิ่งที่ broadcast ต้องการ turbo_stream ที่รับ broadcasted message สำหรับ room นั้น และ div container สำหรับ append messages id ต้องตรงกับ target ของ broadcast

เพิ่มใน message model:

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

วิธีนี้ broadcast ไป room เฉพาะที่สร้าง

เพิ่ม stream, message container และ message form ใน index file:

# within the @single_room condition in app/views/rooms/index.html.erb
<%= turbo_stream_from @single_room %>
<div id="messages">
</div>
<%= render partial: 'layouts/new_message_form' >

สร้าง message partial ที่จะ broadcast แสดง username sender เฉพาะ room public

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

จาก console สร้าง message จะเห็น broadcast ไป room ด้วย template ที่ assigned

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

เพื่อให้สร้าง message จาก dashboard ได้ เพิ่ม create method ใน MessagesController

# 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

ผลลัพธ์: สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

เห็น messages append แต่ถ้าไป room อื่น ดูเหมือนสูญเสีย messages ใน room ก่อนหน้าตอนกลับมา เพราะไม่ได้ fetch messages ของ room มาแสดง ทำได้โดยใน RoomsController show method เพิ่ม variable fetch messages ทั้งหมดของ room และใน index page render fetched messages ยังเห็น message form ไม่ clear หลังส่ง จะจัดการด้วย Stimulus ต่อไป

# in the show method of app/controllers/rooms_controller.rb
@messages = @single_room.messages
# within the div with id of 'messages'
<%= render @messages %>

ตอนนี้แต่ละ room จะมี messages โหลดตอนเข้า

ทำให้ presentable มากขึ้น align messages ของ current user ขวา และคนอื่น ซ้าย วิธีตรงไปตรงมาคือ assign classes ตาม condition message.user == current_user แต่ local variables ไม่พร้อมสำหรับ streams ดังนั้นสำหรับ broadcasted message ไม่มี current_user ทำยังไง? Assign class ให้ message container based on message sender id และใช้ current_user helper method เพิ่ม style ใน application.html.erb วิธีนี้ ถ้า current user id เป็น 2 class ใน style tag จะเป็น .msg-2 ตรงกับ class ใน message partial เมื่อ sender เป็น current user

# 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 styling:

# 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 }

ใน head tag ของ 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 tag ให้ background-color เพราะอยาก override สำหรับ current user

Chats ตอนนี้: สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

Private Chat

ส่วนใหญ่ของ private chats ทำเสร็จระหว่าง group chat setup เหลือแค่:

  • สร้าง private room สำหรับ private chat เมื่อ route ไป user เฉพาะ หาก room ไม่มีอยู่
  • สร้าง participants สำหรับ rooms นั้น เพื่อไม่ให้ intruder ส่ง messages ไป rooms นั้น แม้จาก console
  • ป้องกัน private rooms ใหม่ broadcast ไป room list
  • แสดง username แทน room name เวลา private chat

ใน routing ไป user เฉพาะ current_user บ่งบอกว่าอยาก private chat กับ user นั้น ดังนั้นใน UsersController เช็คว่า private room มีอยู่ระหว่างทั้งสองหรือไม่ หากมี กลายเป็น @single_room variable มิฉะนั้นสร้าง เราจะสร้าง special room name สำหรับแต่ละ private chat room เพื่อ reference ตอนต้องการ รวม variables ที่ index page ต้องการใน show method เพื่ออยู่หน้าเดียวกัน

# 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

เห็นว่าต้องเพิ่ม create_private_room method ใน Room model สร้าง private room และ participants นำไปสู่การสร้าง model ใหม่ Participant ระบุ user และ private room ที่เขา/เธอ belong

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

ใน room model เพิ่ม create_private_room method และเปลี่ยน after_create ให้ broadcast เฉพาะไม่ใช่ private room

# 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

ป้องกัน users อื่นส่ง messages ไป private room เมื่อไม่ใช่ participants เพิ่ม before_create check ใน Message model สำหรับ private rooms ยืนยันว่า sender เป็น participant จริงๆ ก่อนสร้าง message ควรสังเกตจาก dashboard ไม่可能 ส่ง message ไป private room หากไม่ใช่ participant เพราะแค่คลิก username room ก็สร้างให้ทั้งคู่ check นี้แค่ extra security เพราะ message สร้างโดย non-participant จาก console ได้

# 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

แสดง username แทน room name เวลา private chat ระบุใน index page ว่า หาก @user variable present ให้แสดง username นั่นคือ variable นี้มีแค่ใน UsersController show method นำไปสู่ h4 tag แสดง room name เปลี่ยนเป็น:

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

ตอน navigate ไป user จะเห็น username แทน room name และส่ง/รับ messages ได้ เพิ่ม sign-out link ใน home page

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

เห็นด้านล่าง สำหรับ private chats username sender ไม่แสดง เราระบุ messages ของตัวเองจากตำแหน่ง

สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

Stimulus

ใช้ stimulus clear forms เพราะ full-page re-render ไม่เกิดขึ้น และ forms ไม่ clear บน new model instance creation

bundle add stimulus-rails
rails stimulus:install

เพิ่มไฟล์เหล่านี้ ตามภาพ สร้าง Live Chat App ใน Rails ด้วย ActionCable และ Turbo: คู่มือทีละขั้นตอน

สร้าง reset_form_controller.js reset forms เพิ่ม function:

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

เพิ่ม data attribute ใน forms ระบุ controller และ action

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

ตัวอย่าง form_with tag ของ message form เปลี่ยนเป็น:

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

แค่นี้ forms clear หลังสร้าง message หรือ room ใหม่ ควรทราบ stimulus action "ajax:success->reset-form#reset" ก็ clear form เมื่อ ajax:success event เกิดขึ้น

สรุป

ใน app นี้เราโฟกัส append action ของ Turbo Streams แต่ไม่ใช่ทั้งหมด Turbo Streams มี 5 actions: append, prepend, replace, update, remove สำหรับ implement deleting และ updating chat messages real-time actions นี้จะช่วย และความรู้ Turbo frames อาจจำเป็น สำคัญคือสำหรับ apps ที่ depends WebSocket updates สำหรับ features บางอย่าง บน poor connections หรือ server issues WebSocket อาจ disconnect แนะนำใช้ Turbo Streams ใน apps เฉพาะเมื่อ extremely important เท่านั้น