BroadcastHub
BroadcastHub is a reusable Action Cable broadcasting layer for Rails 5/6 apps that use server-rendered HTML and Sprockets. It replaces model-level Turbo stream helpers with an explicit payload contract sent over BroadcastHub::StreamChannel.
1) What BroadcastHub is
- Rails engine (
broadcast_hub) scoped to Rails>= 5.2,< 7.0 - Server concern (
BroadcastHub::Broadcaster) for model callbacks and payload publishing - Generic Action Cable channel (
BroadcastHub::StreamChannel) with authorization and stream key resolution - Browser helpers (
BroadcastHub.SubscriptionandBroadcastHub.JQueryController) for applying append/prepend/update/remove actions
BroadcastHub is designed to work without turbo-rails.
2) Installation in host app
Add the engine gem to the host app Gemfile:
gem 'broadcast_hub', path: 'engines/broadcast_hub'
Install dependencies, then generate the initializer template:
bundle install
bin/rails generate broadcast_hub:install
This creates config/initializers/broadcast_hub.rb.
3) Initializer configuration
Minimum required settings:
allowed_resources: allowlist of resource keys clients can subscribe toauthorize_scope: lambda that decides if the Action Cable connection can subscribestream_key_resolver: lambda that maps subscription context to a stream name used by both channel + model broadcaster
Authenticated example:
BroadcastHub.configure do |config|
config.allowed_resources = %w[todo]
config. = lambda do |context|
context.current_user.present?
end
config.stream_key_resolver = lambda do |context|
"resource:#{context.resource_name}:user:#{context.current_user.id}"
end
end
No-auth/session example:
BroadcastHub.configure do |config|
config.allowed_resources = %w[todo]
config. = lambda do |context|
context.session_id.present?
end
config.stream_key_resolver = lambda do |context|
"resource:#{context.resource_name}:session:#{context.session_id}"
end
end
If your Action Cable connection does not expose current_user, expose a safe identifier (for example session_id) in ApplicationCable::Connection.
4) Model integration
Include the concern and declare broadcast settings in each model:
class Todo < ApplicationRecord
include BroadcastHub::Broadcaster
broadcast_to :todo, partial: 'todos/partials/todo', target: '#todos'
end
broadcast_to wires callbacks:
after_create_commit-> appendafter_update_commit-> updateafter_destroy_commit-> remove
Optional context hook for stream-key alignment (recommended when keys depend on tenant/user/session):
def broadcast_hub_stream_key_context_attributes
{
tenant_id: nil,
current_user: user,
session_id: nil,
params: {}
}
end
5) Client-side integration (Sprockets)
Require BroadcastHub in app/assets/javascripts/application.js:
//= require broadcast_hub/index
Basic subscription wiring (compatible with this repo style):
(function (global) {
function wireTodoChannel(consumer, $) {
var controller = new BroadcastHubJQueryController($);
var subscription = new BroadcastHubSubscription(consumer, controller);
return subscription.subscribe('todo');
}
if (global.App && global.App.cable && global.jQuery) {
global.App.todo_channel = wireTodoChannel(global.App.cable, global.jQuery);
}
})(this);
BroadcastHubSubscription sends { channel: 'BroadcastHub::StreamChannel', resource: 'todo' } and the controller applies incoming payloads to the DOM.
6) Payload contract
Payloads emitted by BroadcastHub::PayloadBuilder follow this shape:
{
"version": 1,
"action": "append",
"target": "#todos",
"content": "<div id=\"todo_1\">...</div>",
"id": "todo_1"
}
Field meaning:
action: one ofappend,prepend,update,removetarget: CSS selector used as insertion/update/remove targetcontent: rendered HTML for append/prepend/update (typicallynullon remove)id: DOM id used by update/remove fast-path replacementversion: payload contract version fromBroadcastHub.configuration.payload_version