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:
root
2026-05-17 15:25:49 +02:00
parent efccbc2064
commit 1f52448253
9 changed files with 294 additions and 6 deletions
+51
View File
@@ -0,0 +1,51 @@
# frozen_string_literal: true
module Mozo
module Broadcaster
# Drop-in replacement for Mozo::Broadcaster::Faye that uses
# Rails' built-in ActionCable instead of an external Faye process.
#
# Benefits over Faye:
# - Async by default (ActionCable.server.broadcast is non-blocking)
# - No extra gem / process / port to manage
# - Integrated with Rails authentication (cookies, sessions)
# - WebSocket native (no long-polling fallback needed with modern browsers)
#
# Channel naming is kept compatible with the existing Faye convention:
# /user/:uid → user_<uid>
# /supplier/:sid → supplier_<sid>
#
# To use:
# Set Mozo.broadcaster = Mozo::Broadcaster::ActionCable.new
# in config/initializers/mozo_settings.rb
#
class ActionCable
CHANNEL_PREFIX_REMAP = {
%r{^/user/(.+)$} => 'user_\1',
%r{^/supplier/(.+)$} => 'supplier_\1'
}.freeze
def broadcast(message)
channel = message[:channel] || message['channel']
data = message[:data] || message['data']
remapped = remap_channel(channel)
return unless remapped
::ActionCable.server.broadcast(remapped, data)
rescue => e
Rails.logger.error("[ACTION_CABLE][ERROR] #{e.message}")
end
private
def remap_channel(channel)
CHANNEL_PREFIX_REMAP.each do |pattern, replacement|
return channel.sub(pattern, replacement) if channel.match?(pattern)
end
Rails.logger.warn("[ACTION_CABLE] Unknown channel format: #{channel}")
nil
end
end
end
end