0.0
The project is in a healthy, maintained state
Reusable Action Cable broadcasting engine for Rails 5/6
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

Runtime

>= 5.2, < 7.0
 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:

gem 'broadcast_hub', '~> 0.2.1'

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 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:

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:

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:

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):

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.

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:

<tr id="<%= dom_id(@todo, prefix: 'row', suffix: 'flash') %>">
  <td><%= @todo.title %></td>
</tr>

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:

//= 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.

7) Payload contract

Payloads emitted by BroadcastHub::PayloadBuilder follow this contract:

{
  "version": 1,
  "action": "append",
  "target": "#todos",
  "content": "<div id=\"todo_1\">...</div>",
  "id": "todo_1",
  "meta": {}
}

Dispatch actions extend this payload with event fields:

{
  "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