https://github.com/nemuba/broadcast_hub
BroadcastHub is a reusable Action Cable broadcasting layer for Rails 5/6 apps that use server-rendered HTML and Sprockets.
https://github.com/nemuba/broadcast_hub
actioncable jquery rails ruby
Last synced: 2 months ago
JSON representation
BroadcastHub is a reusable Action Cable broadcasting layer for Rails 5/6 apps that use server-rendered HTML and Sprockets.
- Host: GitHub
- URL: https://github.com/nemuba/broadcast_hub
- Owner: nemuba
- License: mit
- Created: 2026-03-24T03:41:14.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-04-03T19:12:30.000Z (3 months ago)
- Last Synced: 2026-04-03T21:35:32.006Z (3 months ago)
- Topics: actioncable, jquery, rails, ruby
- Language: JavaScript
- Homepage:
- Size: 5.06 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: MIT-LICENSE
Awesome Lists containing this project
README
# 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.Subscription` and `BroadcastHub.JQueryController`) for applying append/prepend/update/remove/dispatch actions
BroadcastHub is designed to work without `turbo-rails`.
## 2) Installation in host app
Add the engine gem to the host app `Gemfile`:
```ruby
gem 'broadcast_hub', '~> 0.2.1'
```
Install dependencies, then generate the initializer template:
```bash
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 to
- `authorize_scope`: lambda that decides if the Action Cable connection can subscribe
- `stream_key_resolver`: lambda that maps subscription context to a stream name used by both channel + model broadcaster
Authenticated example:
```ruby
BroadcastHub.configure do |config|
config.allowed_resources = %w[todo]
config.authorize_scope = 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:
```ruby
BroadcastHub.configure do |config|
config.allowed_resources = %w[todo]
config.authorize_scope = 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:
```ruby
class Todo < ApplicationRecord
include BroadcastHub::Broadcaster
broadcast_to :todo, partial: 'todos/partials/todo', target: '#todos'
end
```
`broadcast_to` wires callbacks:
- `after_create_commit` -> append
- `after_update_commit` -> update
- `after_destroy_commit` -> remove
Optional context hook for stream-key alignment (recommended when keys depend on tenant/user/session):
```ruby
def broadcast_hub_stream_key_context_attributes
{
tenant_id: nil,
current_user: user,
session_id: nil,
params: {}
}
end
```
## 5) Controller helper integration
For controller-triggered realtime updates (for example action-specific highlight/flash events), use `render_broadcast`.
BroadcastHub also exposes `dom_id(record, positional_prefix = nil, prefix: nil, suffix: nil)` in both controllers and views.
- `dom_id(todo)` -> `todo_1`
- `dom_id(todo, :edit)` -> `edit_todo_1`
- `dom_id(todo, prefix: 'row')` -> `row_todo_1`
- `dom_id(todo, suffix: 'highlight')` -> `todo_1_highlight`
- `dom_id(todo, prefix: 'row', suffix: 'highlight')` -> `row_todo_1_highlight`
If both positional prefix and keyword `prefix` are provided, `dom_id` raises `ArgumentError`.
```ruby
class TodosController < ApplicationController
def highlight
respond_to do |format|
format.js { broadcast_todo_highlight }
format.json { broadcast_todo_highlight }
end
end
private
def broadcast_todo_highlight
render_broadcast(
action: 'dispatch',
target: "##{dom_id(current_user.todos.find(params[:id]), prefix: 'row', suffix: 'flash')}",
resource: 'todo',
event_name: 'todo:highlight',
event_data: { id: params[:id] }
)
end
end
```
View usage example:
```erb
<%= @todo.title %>
```
`render_broadcast` options:
- Required: `action`, `target`, `resource`
- For `append|prepend|update`: `partial` is required
- For `remove|dispatch`: `content` is forced to `nil`
- For `dispatch`: `event_name` is required and `event_data` must be a hash when provided
- `id` defaults to a generated UUID when omitted
- Stream authorization/identity is resolved through `BroadcastHub::StreamKeyResolver.resolve!` using `BroadcastHub::StreamKeyContext`
## 6) Client-side integration (Sprockets)
Require BroadcastHub in `app/assets/javascripts/application.js`:
```js
//= require broadcast_hub/index
```
Basic subscription wiring (compatible with this repo style):
```js
(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.
## 7) Payload contract
Payloads emitted by `BroadcastHub::PayloadBuilder` follow this contract:
```json
{
"version": 1,
"action": "append",
"target": "#todos",
"content": "
...",
"id": "todo_1",
"meta": {}
}
```
Dispatch actions extend this payload with event fields:
```json
{
"version": 1,
"action": "dispatch",
"target": "#todos",
"content": null,
"id": "todo_1",
"meta": {},
"event_name": "todo:highlight",
"event_data": { "id": "todo_1" }
}
```
Field meaning:
- `action`: one of `append`, `prepend`, `update`, `replace`, `remove`, `dispatch`
- `target`: CSS selector used as insertion/update/remove/dispatch target
- `content`: rendered HTML for append/prepend/update/replace (typically `null` on remove/dispatch)
- `id`: DOM id used by update/remove fast-path replacement
- `meta`: optional metadata hash (defaults to `{}`)
- `event_name`: required when `action` is `dispatch`; event name passed to jQuery `trigger`
- `event_data`: optional hash payload for `dispatch`; delivered as trigger argument data
- `version`: payload contract version from `BroadcastHub.configuration.payload_version`
Dispatch-specific notes:
- `event_name` and `event_data` are included only when `action` is `dispatch`
- `event_data` must be a hash when provided