来到倒数第二天啦!感动流泪O_Q
前两天里用Vuex状态管理的方式实作编辑
和删除
ticket
本日待实作的功能:利用Action Cable广播,在任何的浏览器登入,都会同步显示编辑和删除的结果~让使用者有即时互动的体验!
如下图所示,我开了两个浏览器(Safari和chrome),无论在哪个浏览器编辑
或删除
ticket,另一个浏览器都会即时产生效果~
WebSocket是能让浏览器(Client端)与服务器(Server端)持续进行双向资料传递
的通讯协定,Client端用Ajax非同步方式做请求, Server端也能主动发送Client所需要的资料。
相信大家都有听过HTTP吧~HTTP是基於TCP(Transmission Control Protocol, 传输控制协定)进行「三向交握(3-way handshake)建立连线(A方提出请求、B方确认A的请求并提出自己请求、A方再确认一次B的请求,总共三次。)
而WebSocket是进阶的网路应用层协议(在2011年由IETF标准化)。如果Client端初始化时HTTP标头中含有WebSocket的资讯,当连线Client端到Server端,Server端会将HTTP连线升级为WebSocket的连接,并返回同样包含websocket标头资讯的HTTP回应,就能立即实现持续的双向沟通
,因此WebSocket常用来实作可以互相传讯息的聊天室。
ActionCable是建构於WebSocket上、Pub(消息发布者)/Sub(消息订阅者)模型的Ruby on Rails框架,Publisher和Subscriber会以javascript透过非同步(async)的方式传递讯息,使讯息传递不需等待回应就可进行後续操作(想像聊天的时候,你不用等收到别人讯息的时候才能传出自己的讯息),非常便利!接下来今天的文章开始参考Rails Guides: Action Cable ,并使用Redis
这款带有Pub/Sub
功能的资料库实作。
通常看到网路上的教学步骤,都会请你先new一个rails专案、建立User model以及必要的controller。
由於我们现在这个铁人赛专案已经有了很多个controller(kanban, column, ticket...XD),就直接挑比较符合业务逻辑的controller加上channel。
首先我来rails g channel
建立一个channel,名叫column
rails g channel column
Running via Spring preloader in process 70377
invoke rspec
create spec/channels/column_channel_spec.rb
create app/channels/column_channel.rb
接着来修改channels/column_channel.rb
这个档案,在里面指定订阅的频道是刚刚建立的column
class ColumnChannel < ApplicationCable::Channel
def subscribed
stream_from "column"
end
def unsubscribed
end
end
想像一个软件有群组聊天以及私人一对一聊天的功能,我们需要符合身份的人(也就是有订阅
这个频道的人),有值的话才能回传给他特定的讯息,不然就拒绝连线。
那要怎麽实作这个功能呢?
最一开始使用Rails的devise gem
制作注册登入系统,里已经提供给我们适当的环境变数env["warden"]
。
在这里,把它透过byebug
印出来看看:
10:49:52 web.1 | (byebug) env["warden"]
10:49:52 web.1 | Warden::Proxy:70111027317460 @config={:default_scope=>:user, :scope_defaults=>{}, :default_strategies=>{:user=>[:rememberable, :database_authenticatable]}, :intercept_401=>false, :failure_app=>#<Devise::Delegator:0x00007f87fa2cc638>}
10:49:52 web.1 | (byebug) env["warden"].user
10:50:30 web.1 | #<User id: 1, email: "tingtinghsu[at]秘密", created_at: "2020-10-01 01:43:03", updated_at: "2020-10-01 07:12:27", name: "Ting">
我们在channels/application_cable/connection.rb
进行连线前的身份验证,如果env["warden"].user
有值,指定给current_user
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
#这里引入current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if current_user = env["warden"].user
current_user
else
reject_unauthorized_connection
end
end
end
end
end
接下来是浏览器端了!
虽然这一份js档案不是我们自己写的,不过还是可以来研究一下大神们开发的架构,这个档案会让consumer预设与server的cable做连线。
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
订阅者
:建立 column_channel.jsConsumer连线後成为Subscriber後就会进来这里。有三个部分:connected()
、received(data)
、disconnected()
我们可以在浏览器的dev tool里console分别印出连线状况,帮助debug。
app/frontend/channel/column.js
import consumer from "./consumer"
console.log("loading")
consumer.subscriptions.create("ColumnChannel", {
connected() {
console.log("connected")
},
received(data) {
console.log("Welcome to ColumnChannel")
console.log(data)
if(data.commit){
//等等会用到这个区块
}
},
disconnected() {
console.log("disconnected")
}
});
把专案的编辑删除ticket的controller动作结合action cable,资料更动成功的话,透过ActionCable.server.broadcast
对column channel
做广播,要commit的那一包payload为转为string的:show.json
。
def update
respond_to do |format|
if @ticket.update(ticket_params)
ActionCable.server.broadcast("column", { commit: 'UPDATE_TICKET', payload: render_to_string(:show, format: :json)})
format.json { render :show, status: :ok}
else
format.json { render json: @ticket.errors, status: :unprocessable_entity }
end
end
end
def destroy
@ticket.destroy
respond_to do |format|
puts "destroy success"
ActionCable.server.broadcast("column", { commit: 'DELETE_TICKET', payload: render_to_string(:show, format: :json)})
format.json { head :no_content }
end
end
ps.这边的点我卡了几个小时,因为一直没有render出自己要的json档,後来发现要把scaffold长出来没用到的html.erb先删掉,Rails才不会找错档案。
记得把原本Vuex action内的commit注解掉,因为现在我们已经透过controller进行全域的广播了(不然会操作的那一方浏览器会commit两次)
actions: {
updateTicket({ commit }, {id, name}){
// 略
Rails.ajax({
url: `/kanbans/${el.dataset.kanbanid}/tickets/${id}`,
type: 'PUT',
data,
dataType: 'json',
success: result => {
// commit("UPDATE_TICKET", result);
console.log(result);
},
error: error => {
console.log(error);
}
});
},
deleteTicket({ commit }, {ticket_id, column_id}){
//略
Rails.ajax({
url: `/kanbans/${el.dataset.kanbanid}/tickets/${ticket_id}`,
type: 'DELETE',
dataType: 'json',
success: result => {
// commit("DELETE_TICKET", {ticket_id, column_id});
},
error: error => {
console.log(error)
}
});
},
Subscriber
订阅者处理需要commit的data$store
变成全域变数之前做专案时,如果是透过webpacker引入jQuery套件,有一个偷吃步的方法可以把$
字符号设为全域都可使用
就是在application.js
加上window.$ = $
import $ from 'jquery'
window.$ = $
那麽为了让我们可以在Action Cable
的client端也可以对store做事情,
我们如法炮制,在application.js
这个Vue的挂载点加上这句:
window.$store = store;
Subscriber
订阅者收到资料received(data)
vuex限制commit
只能传2个参数,第一个是function名称,第二个用object包起来的参数(称为payload)。
我们把收到的data,利用window.$store.commit(data.commit, JSON.parse(data.payload));
重新渲染自己的页面
app/frontend/channel/column.js
import consumer from "./consumer"
consumer.subscriptions.create("ColumnChannel", {
connected() {
},
received(data) {
if(data.commit){
console.log("data commit!")
window.$store.commit(data.commit, JSON.parse(data.payload));
}
},
disconnected() {
}
});
完成搂!在哪个浏览器编辑
或删除
ticket,另一个浏览器都会即时产生效果~
实作完今天的单元後,又对於WebSocket这个浏览器(Client端)与服务器(Server端)进行双向资料传递
的通讯协定更加清晰了!
心得:
ActionCable.server.broadcast
,算是一个自己的大突破~~本来预计想做的是拖拉的action cable即时效果(但还没克服bug)紧急换成实作删除ticket
和修改ticket
的即时互动效果~终於在自己规定的时限内研究出来+把文章写好。Ref:
找到一个有趣的程序码,改了一下,可截取Video画面,存成一张张图片。 进行中想要中断执行,可按 E...
@csrf_exempt def callback(request): if request.met...
回圈 for 回圈 for 回圈,很适合用来处理数值会依照次数,有「递增」或「递减」的变化 范例如下...
这边有一个javascript变数: var subtitles = {{ json_dual }}...
此篇延续 Bootstrap 客制化 Sass utilities(上)最後尚未介绍的 gener...