dchuk/rails_ai_agents · Archived

action-cable-patterns

Implements real-time features with Action Cable and WebSockets. Use when adding live updates, chat features, notifications, real-time dashboards, or when user mentions Action Cable, WebSockets, channels, or real-time.

First seen Feb 13, 2026

Installation

$ npx skills add dchuk/rails_ai_agents --skill action-cable-patterns

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from dchuk/rails_ai_agents · top by installs.

npx skills add dchuk/rails_ai_agents

Browse all from dchuk/rails_ai_agents

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

License LICENSE
Default branch main
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Write, Edit, Bash, Glob, Grep

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,188 B
  • docs SUMMARY.md 246 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 2 installs

SKILL.md

Action Cable Patterns for Rails 8

Overview

Action Cable integrates WebSockets with Rails:

  • Real-time updates without polling
  • Server-to-client push notifications
  • Chat and messaging features
  • Live dashboards and feeds

Quick Start

# config/cable.yml
development:
  adapter: async

test:
  adapter: test

production:
  adapter: solid_cable  # Rails 8 default

Connection Authentication

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private

    def find_verified_user
      if session_token = cookies.signed[:session_token]
        if session = Session.find_by(token: session_token)
          session.user
        else
          reject_unauthorized_connection
        end
      else
        reject_unauthorized_connection
      end
    end
  end
end

Channel Patterns

Pattern 1: Notifications Channel

# app/channels/notifications_channel.rb
class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end

  def self.notify(user, notification)
    broadcast_to(user, {
      type: "notification",
      id: notification.id,
      title: notification.title,
      body: notification.body
    })
  end
end

Pattern 2: Resource Updates Channel

# app/channels/events_channel.rb
class EventsChannel < ApplicationCable::Channel
  def subscribed
    @event = Event.find(params[:event_id])

    if authorized?
      stream_for @event
    else
      reject
    end
  end

  def self.broadcast_update(event)
    broadcast_to(event, {
      type: "update",
      html: ApplicationController.renderer.render(
        partial: "events/event", locals: { event: event }
      )
    })
  end

  private

  def authorized?
    EventPolicy.new(current_user, @event).show?
  end
end

Pattern 3: Integration with Turbo Streams

# app/models/comment.rb
class Comment < ApplicationRecord
  after_create_commit -> {
    broadcast_append_to(
      [event, "comments"],
      target: "comments",
      partial: "comments/comment"
    )
  }

  after_destroy_commit -> {
    broadcast_remove_to([event, "comments"])
  }
end
<%# app/views/events/show.html.erb %>
<%= turbo_stream_from @event, "comments" %>

<div id="comments">
  <%= render @event.comments %>
</div>

Broadcasting from Services

module Events
  class UpdateService
    def call(event, params)
      event.update!(params)
      EventsChannel.broadcast_update(event)
      DashboardChannel.broadcast_stats(event.account)
      success(event)
    end
  end
end

Testing Channels

Channel Test (Minitest)

# test/channels/notifications_channel_test.rb
require "test_helper"

class NotificationsChannelTest < ActionCable::Channel::TestCase
  setup do
    @user = users(:one)
    stub_connection(current_user: @user)
  end

  test "subscribes successfully" do
    subscribe
    assert subscription.confirmed?
  end

  test "streams for the current user" do
    subscribe
    assert_has_stream_for @user
  end

  test "broadcasts notification to user" do
    subscribe
    notification = notifications(:one)

    assert_broadcast_on(
      NotificationsChannel.broadcasting_for(@user),
      hash_including(type: "notification")
    ) do
      NotificationsChannel.notify(@user, notification)
    end
  end
end

Channel with Authorization Test

# test/channels/events_channel_test.rb
require "test_helper"

class EventsChannelTest < ActionCable::Channel::TestCase
  setup do
    @user = users(:one)
    @event = events(:one) # belongs to @user's account
    @other_event = events(:other_account)
    stub_connection(current_user: @user)
  end

  test "subscribes to authorized event" do
    subscribe(event_id: @event.id)
    assert subscription.confirmed?
    assert_has_stream_for @event
  end

  test "rejects unauthorized event" do
    subscribe(event_id: @other_event.id)
    assert subscription.rejected?
  end
end

Connection Test

# test/channels/connection_test.rb
require "test_helper"

class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
  test "connects with valid session token" do
    user = users(:one)
    session = user.sessions.create!

    connect cookies: { session_token: session.token }

    assert_equal user, connection.current_user
  end

  test "rejects without session token" do
    assert_reject_connection do
      connect
    end
  end
end

Stimulus Controller for Channels

// app/javascript/controllers/chat_controller.js
import { Controller } from "@hotwired/stimulus"
import consumer from "../channels/consumer"

export default class extends Controller {
  static targets = ["messages", "input"]
  static values = { roomId: Number }

  connect() {
    this.channel = consumer.subscriptions.create(
      { channel: "ChatChannel", room_id: this.roomIdValue },
      {
        received: this.received.bind(this),
      }
    )
  }

  disconnect() {
    this.channel?.unsubscribe()
  }

  received(data) {
    if (data.type === "message") {
      this.messagesTarget.insertAdjacentHTML("beforeend", data.html)
      this.messagesTarget.scrollTop = this.messagesTarget.scrollHeight
    }
  }

  send(event) {
    event.preventDefault()
    const body = this.inputTarget.value.trim()
    if (body) {
      this.channel.perform("speak", { body })
      this.inputTarget.value = ""
    }
  }
}

Checklist

  • Connection authentication configured
  • Channel authorization implemented
  • Channel tests written
  • Broadcasting from services/models
  • Client-side subscription set up
  • Turbo Stream integration (if applicable)
  • All tests GREEN