鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了 ActionCable
的使用方式,今天繼續來延伸 ActionCable
,試著用 ActionCable
的基礎做出一個即時的聊天室,夠夠~
我想要做一個即時的聊天室
如果要做到這樣的需求的話,那這樣會需要兩個 channel:一個 channel 負責轉發聊天訊息、一個 channel 負責追蹤在線人數
先產生 ChatController 當作首頁
$ rails g controller ChatController index
create app/controllers/chat_controller.rb
route get 'chat/index'
invoke erb
create app/views/chat
create app/views/chat/index.html.erb
invoke test_unit
create test/controllers/chat_controller_test.rb
invoke helper
create app/helpers/chat_helper.rb
invoke test_unit
將 chat_controller 的 index 設為首頁
# config/routes.rb
root 'chat#index'
再產生聊天用的 Channel,並把 ActionCable 的 url 加上
$ rails g channel chat_channel
invoke test_unit
create test/channels/chat_channel_test.rb
identical app/channels/application_cable/channel.rb
identical app/channels/application_cable/connection.rb
create app/channels/chat_channel.rb
create app/javascript/channels/index.js
create app/javascript/channels/consumer.js
append app/javascript/application.js
append config/importmap.rb
create app/javascript/channels/chat_channel.js
gsub app/javascript/channels/chat_channel.js
append app/javascript/channels/index.js
在 app/views/layout/application.html.erb
加上 action_cable_meta_tag
<!DOCTYPE html>
<html>
<head>
<%= action_cable_meta_tag %>
</head>
<body>
<%= yield %>
</body>
</html>
檢查一下是不是正常,連到 http://127.0.0.1:3000
,看到 Chat 首頁的同時,也要檢查一下 Rails log 有沒有 websocket 的 log
到這邊基本設置完成
實作聊天發送訊息的功能:
新增一支 speak API,收到 API 的同時,就往 ChatChannel 廣播這則收到的訊息
# app/controllers/chat_controller.rb
class ChatController < ApplicationController
def index
end
def speak
ActionCable.server.broadcast('chat_channel', params[:speak_content])
end
end
# config/routes.rb
Rails.application.routes.draw do
post 'chat/speak'
root 'chat#index'
end
接著新增輸入聊天內容的表單,還有顯示聊天記錄的地方
# app/views/chat/index.html.erb
<h1>Simple Chatroom</h1>
<div id="chat-records">
</div>
<%= form_with(url: '/chat/speak', method: :post, remote: true) do |form| %>
<label>Say: </label>
<%= form.text_field :speak_content %>
<%= form.submit :Send %>
<% end %>
確認一下,目前的樣子,隨便打點什麼之後按送出
檢查 server 有沒有收到 /chat/speak
API request,然後有沒有把資料也廣播到 chat_channel
到這邊訊息發送的功能已經完成,接下來要做的是 Client 收到廣播之後的處理
實作聊天接收訊息的功能:
在 app/javascript/channels/chat_channel.js
實作接收到廣播後的功能
我們讓它收到資料後,就把資料用 p
tag 包起來,然後加到 chat-records
的 div 裡面去
// app/javascript/channels/chat_channel.js
import consumer from "channels/consumer"
consumer.subscriptions.create("ChatChannel", {
connected() {
},
disconnected() {
},
received(data) {
var container = document.getElementById('chat-records')
var textElement = document.createElement('p')
textElement.appendChild(document.createTextNode(data))
container.appendChild(textElement)
}
});
我們來送送看訊息,當我們按送出之後,訊息就會即時同步顯示囉
同時開第二個視窗,然後發訊息看看是不是真的會同步
到這邊核心的聊天室功能已經做完囉,不過還是有一些小細節可以優化,像是發送完訊息,輸入框清空、介面美化等等,這邊先不做,不然篇幅太長
一樣先產生新的頻道,因為要統計在線人數,所以就叫做 statistic_channel 了
$ rails g channel statistic_channel
invoke test_unit
create test/channels/statistic_channel_test.rb
identical app/channels/application_cable/channel.rb
identical app/channels/application_cable/connection.rb
create app/channels/statistic_channel.rb
create app/javascript/channels/statistic_channel.js
gsub app/javascript/channels/statistic_channel.js
append app/javascript/channels/index.js
開啟 statistic_channel 的 subscription stream,同時我們希望在有新加入聊天室、有人離開聊天室的時候,都推播現在的聊天室人數,所以要在有人 subscribed、unsubscribed 的時候推播,可以透過 ActionCable.server.connections.length
取得在線人數
注意:如果在 rails console 中執行ActionCable.server.connections.length
的話,會得到 0 的結果,可能是因為 Cable Server 只有運行在 rails server 裡,並沒有在 rails console 中,所以得到的結果會不一樣
class StatisticChannel < ApplicationCable::Channel
def subscribed
stream_from "statistic_channel"
ActionCable.server.broadcast('statistic_channel', { connections: ActionCable.server.connections.length })
end
def unsubscribed
ActionCable.server.broadcast('statistic_channel', { connections: ActionCable.server.connections.length })
end
end
在 app/views/chat/index.html.erb
新增一個 online-users
的位置給在線人數顯示用
<!-- app/views/chat/index.html.erb -->
<h1>Simple Chatroom</h1>
<p id="online-users"></p>
<div id="chat-records">
</div>
<%= form_with(url: '/chat/speak', method: :post, remote: true) do |form| %>
<label>Say: </label>
<%= form.text_field :speak_content %>
<%= form.submit :Send %>
<% end %>
設定接收到 statistic_channel 後的顯示方式,將得到的在線人數塞入 p
tag 的文字
// app/javascript/channels/statistic_channel.js
import consumer from "channels/consumer"
consumer.subscriptions.create("StatisticChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
var onlineUsers = document.getElementById('online-users')
onlineUsers.innerHTML = "現在有 " + data['connections'] + " 人同時在線上!"
}
});
再來打開兩個視窗,將會看到結果~
聊天 和 在線人數 的功能就完成了,而且兩個通道是獨立運作,不會互相干擾
體驗 ActionCable
之後,覺得可玩性很高,目前都還沒有跟 model 做結合,基礎的玩法可以再綁定 User
model,讓使用者先登入後,以使用者的身份發言,並且新增 Message
的 model,把聊天記錄存下來,再來可以新增 Room
model,讓使用者可以根據不同房間來交流,最終可以做成像是 Slack
或 Discord
那樣的應用。
ActionCable
真的是把 server 端的 websocket 建置簡化了很多,學會最基礎的設定之後,就覺得用 ActionCable
來架 websocket server 很方便,不過因為還沒有實際放到 production 的應用,也不知道在部署的時候會不會踩到什麼雷,歡迎有經驗的大大留言分享 XD,我們明天見~