深入探索Rails中的ActionCable应用
立即解锁
发布时间: 2025-09-06 01:56:12 阅读量: 1 订阅数: 44 AIGC 

### 深入探索 Rails 中的 ActionCable 应用
在 Rails 开发中,实时通信是一个常见的需求。ActionCable 为我们提供了强大的实时通信能力,下面将详细介绍如何在不同场景中使用 ActionCable。
#### 1. 创建回调与默认设置
在创建回调时,与常规情况有所不同。对于新记录,通常不会期望具有该记录 `dom_id` 的元素已经存在。创建操作默认会向 ID 为记录复数名称的元素发送追加操作,例如在某些场景中是 `favorites`。不过,这些默认设置是可以被覆盖的。
- **更改操作类型**:可以通过向 `broadcasts_to` 方法传递 `action: prepend` 将追加操作改为前置操作。
- **更改目标元素**:可以通过传递 `target:` 参数或重写名为 `broadcast_target_default` 的类方法来更改追加操作的目标元素。
此外,还有一个名为 `broadcast` 的方法,它接受与 `broadcasts_to` 相同的参数,但假设 ActiveRecord 通道的名称基于 `self`。
#### 2. Stimulus 与 ActionCable 的结合
Turbo ActionCable 助手虽然强大,但并不能覆盖所有 ActionCable 的使用场景。有时,我们可能需要在客户端使用自定义 JavaScript 来使用 ActionCable,可能是因为已经存在特定的端点,或者任务不太符合 Turbo 模式。下面以重建“售罄”功能为例进行说明。
##### 2.1 服务器端设置
要直接使用 ActionCable,需要在服务器和客户端创建对象。服务器端有通道(channels)和连接(connections)。
- **通道**:类似于 Rails 控制器,用于处理服务器通过套接字接收到的数据,并设置发送给客户端的数据结构。
- **连接类**:相对更抽象,通常用于处理授权。Rails 在项目设置时创建了 `ApplicationCable::Connection` 和 `ApplicationCable::Channel` 两个顶级类,可作为基类使用。
首先,使用以下命令生成一些模板代码:
```bash
% rails generate channel ScheduleChannel
```
这将生成以下文件:
- 服务器端文件:`app/channels/schedule_channel.rb`
- 测试文件:`spec/channels/schedule_channel_spec.rb`(如果安装了 RSpec)
- 客户端文件:`app/packs/channels/schedule_channel.js`
需要注意的是,由于 Rails 和 Webpacker 6 之间可能存在不匹配的问题,Rails 可能会尝试在不同目录(`app/javascript`)中创建 JavaScript 文件。如果是这种情况,需要将它们移动到 `app/packs` 目录。
接下来,定义服务器端的通道:
```ruby
# app/channels/schedule_channel.rb
class ScheduleChannel < ApplicationCable::Channel
def subscribed
stream_from("schedule")
end
def unsubscribed
# 通道取消订阅时的清理操作
end
end
```
`subscribed` 和 `unsubscribed` 方法会在新连接订阅和取消订阅通道时自动调用。在这个例子中,我们只需要使用 `subscribed` 方法将新订阅者连接到名为 `schedule` 的流。
要向这个流发送数据,可以在 Rails 程序的任何地方调用 `ActionCable.server.broadcast` 方法:
```ruby
# app/controllers/sold_out_concerts_controller.rb
class SoldOutConcertsController < ApplicationController
def show
concerts = Concert.includes(:venue, gigs: :band).all
sold_out_concert_ids = concerts.select(&:sold_out?).map(&:id)
ActionCable.server.broadcast(
"schedule",
{ soldOutConcertIds: sold_out_concert_ids }
)
render(json: { soldOutConcertIds: sold_out_concert_ids })
end
end
```
默认情况下,订阅会将数据直接发送到客户端,不进行进一步处理。如果需要进一步处理,可以在 `stream_from` 方法中进行设置:
- **使用编码器**:可以设置 `coder: ActiveSupport::JSON`,这样所有传入的消息在传递之前都会被解码为 JSON。
- **使用块**:可以传递一个块,消息会传递给块,块的结果将发送给客户端。
##### 2.2 客户端设置
在客户端,我们需要捕获服务器发送的数据。之前使用 `SoldOutDataController` 类通过轮询服务器来获取售罄音乐会列表的新数据,现在需要对其进行改造以设置 ActionCable 订阅:
```typescript
// app/packs/controllers/sold_out_data_controller.ts
import { Controller } from "stimulus"
import { createConsumer, Channel } from "@rails/actioncable"
export default class SoldOutDataController extends Controller {
static targets = ["concert"]
concertTargets: Array<HTMLElement>
channel: Channel
started: boolean
connect(): void {
if (this.channel) {
return
}
this.started = true
this.channel = this.createChannel(this)
}
createChannel(source: SoldOutDataController): Channel {
return createConsumer().subscriptions.create("ScheduleChannel", {
received({ soldOutConcertIds }) {
source.updateData(soldOutConcertIds)
},
})
}
updateData(soldOutConcertIds: number[]): void {
this.concertTargets.forEach((concertElement: HTMLElement) => {
concertElement.dataset.concertSoldOutValue = soldOutConcertIds
.includes(parseInt(concertElement.dataset.concertIdValue, 10))
.toString()
})
}
}
```
在这个类中,我们导入了 `createConsumer` 和 `Channel` 从 `@rails/actioncable` 库。在 `connect` 方法中,检查通道是否已经创建,如果没有则创建。`createChannel` 方法通过 WebSocket 将客户端连接到服务器,`received` 方法是一个回调,当接收到新数据时会被调用,它将数据传递给 `updateData` 方法进行处理。
整个工作流程如下:
1. 网页请求被路由到 `SoldOutConcertSource` 的 `show` 方法(广播方法调用可以在任何地方)。
2. 广播调用使 ActionCable 向订阅了该流的任何浏览器发送数据。
3. 订阅时定义的 `received` 回调方法会被新数据调用。
4. 回调方法执行相应操作,在这个例子中,它会通知感兴趣的客户端代码售罄音乐会列表发生了变化,客户端代码会相应地更改浏览器 DOM。
#### 3. React 页面中的 ActionCable 应用
目前,音乐会展示页面通过 `fetch` 调用服务器来确定页面上座位的当前状态,并在点击座位后通过 POST 调用将座位保留。我们可以用单个 ActionCable 订阅来替代这两个调用。
##### 3.1 服务器端的 ConcertChannel
服务器端需要创建一个新的 ActionCable 通道 `ConcertChannel`:
```ruby
# app/channels/concert_channel.rb
class ConcertChannel < ApplicationCable::Channel
def subscribed
stream_from("concert_#{params[:concertId]}")
end
def unsubscribed
# 通道取消订阅时的清理操作
end
def added_to_cart(data)
cart = ShoppingCart.find_or_create_by(user_id: data["userId"])
cart.add_tickets(
```
0
0
复制全文
相关推荐









