feat(broadcasting): add ActionCable adapter + fix model broadcast anti-pattern
- Add Mozo::Broadcaster::ActionCable as drop-in Faye replacement - Fix model_broadcast.rb: delegate to Mozo directly instead of ApplicationController.new (memory-unsafe anti-pattern) - Add Broadcastable concern for clean model-side broadcasting - ActionCable config: async adapter, cable.yml, WebSocket endpoint - MozoChannel with per-entity authorization (user/supplier/employee) - Connection auth via auth_token (matches existing auth pattern) - Mount /cable WebSocket in routes - Add broadcasting-migration.md with Faye→ActionCable guide
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
# Broadcasting: Faye → ActionCable Migration Guide
|
||||
|
||||
## Current state
|
||||
|
||||
```
|
||||
Model (SimplyStored::Couch)
|
||||
→ ApplicationController.new.broadcast_user # ⚠️ anti-pattern
|
||||
→ Mozo.broadcast_user
|
||||
→ Mozo::Broadcaster::Faye.new.broadcast # HTTP POST to Faye
|
||||
→ Faye server (Thin, port 9296)
|
||||
→ WebSocket → browser clients
|
||||
```
|
||||
|
||||
## Target state
|
||||
|
||||
```
|
||||
Model (Broadcastable concern)
|
||||
→ Mozo.broadcast_user
|
||||
→ Mozo::Broadcaster::ActionCable.new.broadcast # in-process async
|
||||
→ ActionCable (Rails built-in)
|
||||
→ WebSocket → browser clients
|
||||
```
|
||||
|
||||
## What this branch adds
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `lib/mozo/broadcaster/action_cable.rb` | Drop-in ActionCable broadcaster adapter |
|
||||
| `config/cable.yml` | ActionCable configuration (async for single-server) |
|
||||
| `app/channels/application_cable/connection.rb` | WebSocket auth via auth_token |
|
||||
| `app/channels/mozo_channel.rb` | Channel authorization for user/supplier/employee |
|
||||
| `app/models/concerns/broadcastable.rb` | Clean module for models (replaces old monkey-patch) |
|
||||
| `config/routes.rb` | Mounts `/cable` WebSocket endpoint |
|
||||
| `config/initializers/model_broadcast.rb` | Fixed: delegates to Mozo directly (no more `ApplicationController.new`) |
|
||||
|
||||
## How to switch
|
||||
|
||||
### 1. Server (one-line change)
|
||||
|
||||
In `config/initializers/mozo_settings.rb`, change:
|
||||
|
||||
```ruby
|
||||
# Mozo.broadcaster = Mozo::Broadcaster::Faye.new # old
|
||||
Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new # new
|
||||
```
|
||||
|
||||
### 2. Client (mozo-user / mozo-supplier)
|
||||
|
||||
**Old Faye client (conceptual):**
|
||||
```js
|
||||
var client = new Faye.Client('https://events.mozo.bar/faye');
|
||||
client.subscribe('/user/123', function(msg) { ... });
|
||||
```
|
||||
|
||||
**New ActionCable client:**
|
||||
```js
|
||||
// Using @rails/actioncable npm package
|
||||
import { createConsumer } from "@rails/actioncable";
|
||||
|
||||
const consumer = createConsumer(
|
||||
`wss://mozo.bar/cable?auth_token=${authToken}`
|
||||
);
|
||||
|
||||
consumer.subscriptions.create(
|
||||
{ channel: "MozoChannel", id: "user_123" },
|
||||
{
|
||||
received(data) {
|
||||
// data = { event: "list_closed", data: { id: 42 } }
|
||||
handleEvent(data.event, data.data);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### 3. Remove Faye
|
||||
|
||||
Once stable:
|
||||
- Remove `gem 'faye'` from Gemfile
|
||||
- Remove `faye/` directory
|
||||
- Remove nginx `events.mozo.bar` vhost
|
||||
- Stop the Faye Thin process
|
||||
|
||||
## Benefits
|
||||
|
||||
- **No extra process** — ActionCable runs inside Puma
|
||||
- **Async** — `broadcast` is non-blocking
|
||||
- **Simpler deploys** — one less service to manage
|
||||
- **WebSocket native** — no long-polling fallback complexity
|
||||
- **Rails auth** — cookies/sessions work automatically
|
||||
Reference in New Issue
Block a user