ruby erb 70 lines · 4 tabs

Broadcasting New Comments to Subscribers over an ActionCable Channel

Shared by codesnips Jul 2026
4 tabs
class CommentsChannel < ApplicationCable::Channel
  def subscribed
    post = find_post
    if post
      stream_for post
    else
      reject
    end
  end

  def unsubscribed
    stop_all_streams
  end

  private

  def find_post
    current_user.readable_posts.find_by(id: params[:post_id])
  end
end
4 files · ruby, erb Explain with highlit

This snippet shows the standard Rails path for pushing a freshly created Comment to everyone watching the same discussion, in real time, over a WebSocket. The pieces are a channel that subscribers connect to, a model callback that enqueues work after commit, and a job that renders and broadcasts the payload.

In CommentsChannel, subscribed calls stream_for @post, which subscribes the connection to a stream whose name is derived from the Post record (via its GlobalID). Using stream_for a model rather than stream_from a hand-built string keeps naming consistent with broadcast_to, so the producer and consumer can never drift apart. The channel guards access by looking up the post through current_user.posts so a client cannot subscribe to a room it may not read; if that lookup fails the subscription is rejected. Client params are untrusted, so find doing the authorization check matters.

The Comment model avoids broadcasting inside the request cycle. after_create_commit is deliberate: it fires only after the database transaction commits, which guarantees the row is actually persisted and visible before any subscriber is told about it. Broadcasting from after_save or after_create risks emitting an event for a comment that a later rollback erases, leaving clients with a phantom message. The callback enqueues CommentBroadcastJob rather than broadcasting inline, moving HTML rendering and Redis publishing off the web thread.

In CommentBroadcastJob, the comment is reloaded defensively (it may have been destroyed between enqueue and execution), then rendered to a partial. broadcast_to targets the same post the channel streamed for, and ApplicationController.renderer produces the exact markup the browser expects, so the client can append the node without a round trip. Rendering server-side keeps a single source of truth for comment markup.

The trade-off is eventual delivery: the job runs asynchronously, so a subscriber sees the comment a moment after commit rather than instantly, and a dead queue means no delivery at all. For most collaborative UIs that latency is invisible and well worth keeping slow work out of the request. This pattern generalizes to any 'notify watchers of a record change' feature — presence, live counters, or activity feeds.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
class Post < ApplicationRecord
  belongs_to :author, class_name: 'User'
  has_many :comments, dependent: :destroy

  scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
  scope :draft, -> { where(published_at: nil) }

ActiveRecord scopes for reusable query logic

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
class PostsController < ApplicationController
  def index
    @posts = Post.includes(:author)
                 .order(created_at: :desc)
                 .page(params[:page])
                 .per(10)

Turbo Frames: infinite scroll with lazy-loading frame

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<form data-controller="query-sync" data-action="change->query-sync#apply">
  <select name="status" class="rounded border p-2">
    <option value="">Any</option>
    <option value="open">Open</option>
    <option value="closed">Closed</option>
  </select>

Filter UI that syncs query params via Stimulus (no front-end router)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Broadcasting New Comments to Subscribers over an ActionCable Channel — share card
Link copied