Beta Version

Real-time Chat Application using Rails Action Cable

A simple real-time chat application built with Rails 7.2 and Action Cable for WebSocket communication

Language Ruby
Framework Rails 7.2
Difficulty Beginner
License MIT
AI Agents
Tags
By bphub
Created: 2026-01-11
17 downloads Download ZIP

README

Rails Chat App with Action Cable

A simple real-time chat application built with Rails 7.2 and Action Cable.

What you can build with this Blueprint

  • Real-time chat application with WebSocket
  • Multiple chat rooms
  • User presence detection (who's online)
  • Message history
  • Simple user authentication
  • Responsive design with Tailwind CSS

Target Stack

  • Language: Ruby 3.2+
  • Framework: Rails 7.2
  • Real-time: Action Cable (WebSocket)
  • CSS: Tailwind CSS
  • DB: PostgreSQL (Docker container)
  • Cache: Redis (for Action Cable adapter)
  • Editor: VS Code
  • AI Agent: Claude 4.5

Features

✅ User authentication (username/password)

✅ Create and join chat rooms

✅ Real-time messaging via WebSocket

✅ User presence (online/offline status)

✅ Message history

✅ Responsive mobile-friendly UI

✅ Emoji support in messages

How to use this Blueprint

For new Rails project

  1. Download and extract this Blueprint
  2. Place example-chat-blueprint/ in your desired project location
  3. Open the directory in VS Code
  4. Let the AI agent read example-chat-blueprint/README.md first
  5. Agent will create Rails app in the same parent directory
  6. Execute implementation tasks in docs/06_tasks.md sequentially

For existing Rails project

  1. Download and extract this Blueprint
  2. Place example-chat-blueprint/ in your Rails project root (same level as app/, config/)
  3. Optionally add to .gitignore if you don't want to commit design docs
  4. Let the AI agent read example-chat-blueprint/README.md
  5. Agent will implement features into your existing Rails app

Directory structure example

my-chat-project/                # Project root
├── example-chat-blueprint/     # This Blueprint (design docs)
│   ├── README.md
│   ├── blueprint.yml
│   └── docs/
│       ├── 01_requirements.md
│       ├── 02_architecture.md
│       ├── 03_data_model.md
│       ├── 04_user_flows.md
│       ├── 05_agent_usage.md
│       ├── 06_tasks.md
│       └── 99_agent_notes.md
└── chat-app/                   # Rails app (created by agent)
    ├── app/
    ├── config/
    ├── db/
    └── ...

Quick Start (After Implementation)

Start Redis (required for Action Cable)

redis-server

Start Rails server

cd chat-app
bin/rails server

Visit the app

Open http://localhost:3000 in your browser

Development Flow

  1. Read docs/05_agent_usage.md to understand how to work with AI agents
  2. Follow tasks in docs/06_tasks.md sequentially
  3. Test each feature after implementation
  4. Refer to docs/99_agent_notes.md for tips and gotchas

Testing Real-time Features

  • Open multiple browser windows (or use incognito mode)
  • Sign in as different users
  • Join the same room and start chatting
  • Messages should appear in real-time across all windows
  • User presence should update when users join/leave

Common Issues

  • WebSocket not connecting: Check Redis is running
  • Messages not appearing: Check browser console for JS errors
  • Cable connection closed: Verify Action Cable is configured in config/cable.yml

Next Steps (Post-MVP)

  • Direct messages between users
  • File/image sharing in chat
  • Message reactions (like, emoji reactions)
  • Typing indicators
  • Read receipts
  • Push notifications
  • Voice/video chat integration
  • Chat room moderation features

Learn More

Requirements

Requirements

Functional Requirements (MVP)

User Authentication

  • Sign up with username and password
  • Sign in to access chat rooms
  • Sign out functionality
  • Current user detection and display
  • Simple password authentication (bcrypt)

Chat Rooms

  • List all rooms (public rooms visible to all logged-in users)
  • Create new room with name and description
  • Join existing room by clicking on it
  • Leave room to go back to room list
  • Room information (name, description, participant count)

Real-time Messaging

  • Send messages in a chat room
  • Receive messages in real-time via WebSocket
  • Display sender username with each message
  • Timestamp for each message
  • Message history (load previous messages when joining)
  • Scroll to bottom automatically when new messages arrive
  • Emoji support in messages

User Presence

  • Online status - show who's currently in a room
  • User list - display active participants
  • Join/leave notifications - notify when users enter/exit
  • Active user count in room list

UI/UX

  • Responsive design - works on mobile and desktop
  • Clean interface with Tailwind CSS
  • Loading states for async operations
  • Error messages for failed actions
  • Empty states for no messages/rooms
  • Message input with send button and Enter key support

Non-Functional Requirements

Performance

  • Messages delivered within 100ms
  • Handle 100+ concurrent users per room
  • Efficient database queries (n+1 prevention)
  • Redis caching for Action Cable

Security

  • Password hashing with bcrypt
  • CSRF protection (Rails default)
  • WebSocket origin validation
  • Sanitize user input (prevent XSS)
  • Authenticated WebSocket connections

Reliability

  • Graceful WebSocket reconnection
  • Connection status indicator
  • Error recovery for failed sends
  • Data persistence (messages saved to DB)

Usability

  • Intuitive navigation
  • Clear visual hierarchy
  • Accessible keyboard shortcuts
  • Mobile-friendly touch targets
  • Readable text contrast

Out of Scope (v1)

Features Not Included

  • ❌ Direct messages (1-on-1 chat)
  • ❌ File/image uploads
  • ❌ Message editing/deletion
  • ❌ Message reactions
  • ❌ Typing indicators
  • ❌ Read receipts
  • ❌ User profiles/avatars
  • ❌ Email verification
  • ❌ Password reset
  • ❌ OAuth login
  • ❌ Private rooms (invitation-only)
  • ❌ Room ownership/moderation
  • ❌ Search messages
  • ❌ Message threading
  • ❌ Voice/video chat
  • ❌ Push notifications
  • ❌ Mobile app

User Stories

As a visitor

  • I want to sign up with a username so I can join chats
  • I want to sign in with my credentials

As a logged-in user

  • I want to see all available chat rooms
  • I want to create a new room with a name
  • I want to join a room and see message history
  • I want to send messages that appear instantly for others
  • I want to see who else is in the room
  • I want to see new messages in real-time without refreshing
  • I want to be notified when users join/leave the room
  • I want to leave a room and return to the room list
  • I want to sign out when I'm done

Technical Requirements

Rails Version

  • Rails 7.2+
  • Ruby 3.2+

Dependencies

  • bcrypt - password hashing
  • redis - Action Cable adapter
  • tailwindcss-rails - styling
  • turbo-rails - Hotwire Turbo
  • stimulus-rails - JavaScript framework

Database

  • PostgreSQL 14+
  • Proper indexes on foreign keys
  • Timestamps on all tables

Action Cable

  • Redis adapter for production
  • Async adapter for development/test
  • Authenticated cable connections
  • Channel-based messaging

Browser Support

  • Modern evergreen browsers (Chrome, Firefox, Safari, Edge)
  • WebSocket support required
  • JavaScript enabled

Success Criteria

MVP is complete when:

  1. ✅ Users can sign up and sign in
  2. ✅ Users can create and join rooms
  3. ✅ Messages appear in real-time via WebSocket
  4. ✅ Message history loads when joining
  5. ✅ User presence is visible (who's online)
  6. ✅ UI is responsive on mobile and desktop
  7. ✅ No major bugs or crashes
  8. ✅ WebSocket reconnects automatically on disconnect

Quality Metrics

  • Page load time < 2 seconds
  • Message delivery < 100ms
  • Zero data loss on disconnect/reconnect
  • All user inputs sanitized
  • No console errors in browser

Architecture

Architecture

System Overview

┌─────────────────────────────────────────────────────────────┐
│                        Client (Browser)                      │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐     │
│  │   HTML/CSS   │  │  Turbo (JS)  │  │ ActionCable  │     │
│  │  (Tailwind)  │  │   (Hotwire)  │  │  (WebSocket) │     │
│  └──────────────┘  └──────────────┘  └──────────────┘     │
└─────────────────────────────────────────────────────────────┘
                              │
                   HTTP & WebSocket
                              │
┌─────────────────────────────────────────────────────────────┐
│                    Rails Application Server                  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                    Controllers                        │  │
│  │  • RoomsController  • MessagesController             │  │
│  │  • UsersController  • SessionsController             │  │
│  └──────────────────────────────────────────────────────┘  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                   Action Cable                        │  │
│  │  • RoomChannel  • PresenceChannel                    │  │
│  └──────────────────────────────────────────────────────┘  │
│  ┌──────────────────────────────────────────────────────┐  │
│  │                      Models                           │  │
│  │  • User  • Room  • Message                           │  │
│  └──────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┴───────────────┐
              │                               │
┌─────────────────────────┐    ┌─────────────────────────┐
│    PostgreSQL Database   │    │      Redis Cache        │
│  • users                 │    │  • Action Cable adapter │
│  • rooms                 │    │  • WebSocket pub/sub    │
│  • messages              │    │  • User presence        │
└─────────────────────────┘    └─────────────────────────┘

Component Architecture

1. Presentation Layer (Views)

Room List Page

  • Display all available rooms
  • Show active user count per room
  • Create new room form
  • Navigation to sign out

Chat Room Page

  • Message history (scrollable)
  • Message input form
  • Send button
  • User list sidebar (who's online)
  • Leave room button
  • Auto-scroll to latest message

Authentication Pages

  • Sign up form
  • Sign in form
  • Simple, minimal design

2. Controller Layer

RoomsController

# Actions:
# - index: list all rooms
# - show: display chat room with messages
# - create: create new room
# - destroy: delete room (optional)

MessagesController

# Actions:
# - create: post new message (broadcasts via cable)

UsersController

# Actions:
# - new: sign up form
# - create: register new user

SessionsController

# Actions:
# - new: sign in form
# - create: authenticate user
# - destroy: sign out

3. Model Layer

User Model

# Attributes: username, password_digest
# Methods: authenticate (via bcrypt)
# Associations: has_many :messages

Room Model

# Attributes: name, description
# Associations: has_many :messages
# Methods: broadcast_message, active_users_count

Message Model

# Attributes: content, user_id, room_id, created_at
# Associations: belongs_to :user, belongs_to :room
# Validations: presence of content

4. Action Cable Layer

RoomChannel

# Purpose: Handle real-time messaging
# Subscription: User subscribes to a room
# Actions:
#   - speak: broadcast message to all in room
#   - receive: handle incoming messages

PresenceChannel

# Purpose: Track online users
# Actions:
#   - appear: user joined room
#   - disappear: user left room
#   - broadcast_presence: notify all users

Data Flow

Message Sending Flow

1. User types message and clicks Send
                    │
2. JavaScript captures submit event
                    │
3. Turbo submits form via fetch (Ajax)
                    │
4. MessagesController#create receives request
                    │
5. Create Message record in database
                    │
6. Broadcast via RoomChannel
                    │
7. Action Cable pushes to all subscribers
                    │
8. All clients receive via WebSocket
                    │
9. Stimulus controller appends message to DOM
                    │
10. Auto-scroll to bottom

User Presence Flow

1. User joins room (opens chat page)
                    │
2. JavaScript subscribes to RoomChannel
                    │
3. Channel broadcasts "user_joined" event
                    │
4. All subscribers receive notification
                    │
5. Update online user list in UI
                    │
6. User leaves (closes page/navigates away)
                    │
7. WebSocket disconnect triggers "user_left"
                    │
8. Broadcast to remaining users
                    │
9. Update user list UI

WebSocket Architecture

Connection Lifecycle

Client                          Server (Action Cable)
  │                                      │
  │─────── Establish WebSocket ──────────→
  │                                      │
  │←────── Connection confirmed ─────────│
  │                                      │
  │─────── Subscribe to channel ─────────→
  │                                      │
  │←────── Subscription confirmed ───────│
  │                                      │
  │─────── Send message ──────────────────→
  │                                      │
  │←────── Broadcast to all ─────────────│
  │                                      │
  │       (message appears for all)      │
  │                                      │
  │─────── Unsubscribe ───────────────────→
  │                                      │
  │─────── Close connection ──────────────→

Redis Pub/Sub

Rails Instance 1          Redis           Rails Instance 2
       │                    │                    │
       │──── Publish ───────→                    │
       │    (message)       │                    │
       │                    │──── Broadcast ─────→
       │                    │                    │
       │                    │                    │
       │←─── Subscribe ─────│                    │
       │    (to channel)    │                    │

Security Architecture

Authentication Flow

  1. User submits username/password
  2. Controller verifies via bcrypt
  3. Session created with user_id
  4. Session cookie stored (encrypted)
  5. Subsequent requests authenticated via session

WebSocket Security

  1. Connection identified by session cookie
  2. Only authenticated users can subscribe
  3. User can only send to subscribed channels
  4. Message content sanitized before broadcast

CSRF Protection

  • Rails automatic CSRF tokens
  • Form submissions include authenticity_token
  • WebSocket exempt (session-based auth)

Deployment Architecture

Development

localhost:3000 (Rails)
localhost:6379 (Redis)
PostgreSQL Docker container

Production (Heroku)

Web Dyno (Rails app)
    ↓
Heroku Redis (Action Cable)
    ↓
Heroku Postgres (Database)

Technology Stack

Backend

  • Ruby 3.2+
  • Rails 7.2
  • Action Cable (WebSocket)
  • bcrypt (password hashing)
  • Redis (pub/sub for Action Cable)

Frontend

  • Hotwire Turbo (Ajax navigation)
  • Stimulus.js (lightweight JS framework)
  • Tailwind CSS (utility-first CSS)
  • Native browser WebSocket API

Database

  • PostgreSQL (primary datastore)
  • Redis (WebSocket pub/sub)

Infrastructure

  • Docker (local PostgreSQL)
  • Heroku or Render (deployment)
  • CloudFlare (optional CDN)

Scalability Considerations

Current Capacity (MVP)

  • 100+ concurrent users per room
  • 1000+ total active connections
  • Vertical scaling (larger dyno/server)

Future Scaling (Post-MVP)

  • Horizontal scaling with load balancer
  • Separate Action Cable servers
  • Redis cluster for pub/sub
  • Database read replicas
  • CDN for static assets

Performance Optimizations

Database

  • Indexes on foreign keys (userid, roomid)
  • Pagination for message history
  • Eager loading to prevent N+1 queries

Caching

  • Redis for Action Cable adapter
  • HTTP caching headers
  • Fragment caching for room list

WebSocket

  • Efficient message serialization (JSON)
  • Debounce typing indicators (future)
  • Batch presence updates

Monitoring & Debugging

Key Metrics

  • WebSocket connection count
  • Message delivery latency
  • Database query time
  • Redis memory usage
  • Error rate

Tools

  • Rails logs (development)
  • Heroku logs (production)
  • Redis CLI for debugging
  • Browser DevTools (Network, Console)

Error Handling

WebSocket Disconnection

  • Automatic reconnection attempt
  • Show connection status in UI
  • Queue messages during disconnection
  • Resend on reconnection

Database Errors

  • Retry transient failures
  • Log errors for debugging
  • Show user-friendly error messages
  • Graceful degradation

Data Model

Data Model

Entity Relationship Diagram

┌─────────────────────┐
│       users         │
├─────────────────────┤
│ id (PK)             │
│ username (unique)   │
│ password_digest     │
│ created_at          │
│ updated_at          │
└─────────────────────┘
           │
           │ 1:N (has_many :messages)
           │
           ↓
┌─────────────────────┐         ┌─────────────────────┐
│      messages       │ N:1     │        rooms        │
├─────────────────────┤─────────├─────────────────────┤
│ id (PK)             │         │ id (PK)             │
│ content             │         │ name (unique)       │
│ user_id (FK)        │         │ description         │
│ room_id (FK)        │←────────│ created_at          │
│ created_at          │   1:N   │ updated_at          │
│ updated_at          │         └─────────────────────┘
└─────────────────────┘

Table Definitions

users

Stores user authentication information.

Column Type Constraints Description
id bigint PRIMARY KEY Auto-incrementing ID
username string NOT NULL, UNIQUE Unique username for login
password_digest string NOT NULL Bcrypt hashed password
created_at datetime NOT NULL Account creation timestamp
updated_at datetime NOT NULL Last update timestamp

Indexes:
- Primary key on id
- Unique index on username

Validations:
- username: presence, uniqueness, length (3-20 characters)
- password: minimum 6 characters (on virtual attribute)


rooms

Stores chat room information.

Column Type Constraints Description
id bigint PRIMARY KEY Auto-incrementing ID
name string NOT NULL, UNIQUE Room display name
description text Optional room description
created_at datetime NOT NULL Room creation timestamp
updated_at datetime NOT NULL Last update timestamp

Indexes:
- Primary key on id
- Unique index on name

Validations:
- name: presence, uniqueness, length (1-50 characters)
- description: length (maximum 200 characters)


messages

Stores chat messages sent in rooms.

Column Type Constraints Description
id bigint PRIMARY KEY Auto-incrementing ID
content text NOT NULL Message content
user_id bigint NOT NULL, FOREIGN KEY References users.id
room_id bigint NOT NULL, FOREIGN KEY References rooms.id
created_at datetime NOT NULL Message sent timestamp
updated_at datetime NOT NULL Last update timestamp

Indexes:
- Primary key on id
- Foreign key index on user_id
- Foreign key index on room_id
- Composite index on (room_id, created_at) for efficient history queries

Validations:
- content: presence, length (1-1000 characters)
- userid: presence
- room
id: presence

Associations:
- belongsto :user
- belongs
to :room


Model Associations

User Model

class User < ApplicationRecord
  has_secure_password

  has_many :messages, dependent: :destroy

  validates :username, presence: true, 
                       uniqueness: true, 
                       length: { in: 3..20 }
  validates :password, length: { minimum: 6 }, allow_nil: true
end

Room Model

class Room < ApplicationRecord
  has_many :messages, dependent: :destroy

  validates :name, presence: true, 
                   uniqueness: true, 
                   length: { in: 1..50 }
  validates :description, length: { maximum: 200 }

  # Returns the 100 most recent messages
  def recent_messages(limit = 100)
    messages.includes(:user).order(created_at: :desc).limit(limit).reverse
  end
end

Message Model

class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room

  validates :content, presence: true, length: { in: 1..1000 }

  # Broadcast message to room subscribers
  after_create_commit do
    broadcast_append_to(
      "room_#{room_id}",
      target: "messages",
      partial: "messages/message",
      locals: { message: self }
    )
  end
end

Database Migrations

Create Users Table

class CreateUsers < ActiveRecord::Migration[7.2]
  def change
    create_table :users do |t|
      t.string :username, null: false
      t.string :password_digest, null: false

      t.timestamps
    end

    add_index :users, :username, unique: true
  end
end

Create Rooms Table

class CreateRooms < ActiveRecord::Migration[7.2]
  def change
    create_table :rooms do |t|
      t.string :name, null: false
      t.text :description

      t.timestamps
    end

    add_index :rooms, :name, unique: true
  end
end

Create Messages Table

class CreateMessages < ActiveRecord::Migration[7.2]
  def change
    create_table :messages do |t|
      t.text :content, null: false
      t.references :user, null: false, foreign_key: true
      t.references :room, null: false, foreign_key: true

      t.timestamps
    end

    add_index :messages, [:room_id, :created_at]
  end
end

Sample Data (Seeds)

# Create sample users
alice = User.create!(
  username: "alice",
  password: "password123"
)

bob = User.create!(
  username: "bob",
  password: "password123"
)

# Create sample rooms
general = Room.create!(
  name: "General",
  description: "General discussion room"
)

random = Room.create!(
  name: "Random",
  description: "Random topics and off-topic chat"
)

rails = Room.create!(
  name: "Rails Help",
  description: "Get help with Ruby on Rails development"
)

# Create sample messages
Message.create!(
  user: alice,
  room: general,
  content: "Welcome to the General chat room!"
)

Message.create!(
  user: bob,
  room: general,
  content: "Thanks! Happy to be here."
)

Query Patterns

Common Queries

Get all rooms with message count:
ruby
Room.left_joins(:messages)
.group(:id)
.select('rooms.*, COUNT(messages.id) as messages_count')
.order(created_at: :desc)

Get recent messages for a room:
ruby
room.messages
.includes(:user)
.order(created_at: :desc)
.limit(100)
.reverse

Get user's message count:
ruby
user.messages.count

Find room by name:
ruby
Room.find_by(name: "General")

Performance Considerations

Indexes Strategy

  • Primary keys: Automatic B-tree index for fast lookups
  • Foreign keys: Indexed for efficient joins
  • Unique constraints: Username and room name for data integrity
  • Composite index: (roomid, createdat) for paginated message queries

N+1 Query Prevention

# ❌ Bad - N+1 queries
messages.each { |m| puts m.user.username }

# ✅ Good - Eager loading
messages.includes(:user).each { |m| puts m.user.username }

Pagination

# Use kaminari or pagy gem
messages.order(created_at: :desc).page(params[:page]).per(50)

Data Integrity

Foreign Key Constraints

  • messages.user_idusers.id (CASCADE on delete)
  • messages.room_idrooms.id (CASCADE on delete)

Dependent Destroy

  • When a user is deleted, all their messages are deleted
  • When a room is deleted, all its messages are deleted

Validations

  • Username uniqueness at database and application level
  • Room name uniqueness at database and application level
  • Message content presence
  • Foreign key presence

Future Enhancements

Additional Tables (Post-MVP)

room_memberships (for private rooms)
ruby
create_table :room_memberships do |t|
t.references :user, null: false, foreign_key: true
t.references :room, null: false, foreign_key: true
t.string :role, default: "member" # member, moderator, admin
t.timestamps
end

reactions (for message reactions)
ruby
create_table :reactions do |t|
t.references :user, null: false, foreign_key: true
t.references :message, null: false, foreign_key: true
t.string :emoji, null: false
t.timestamps
end

attachments (for file uploads)
ruby
create_table :active_storage_attachments do |t|
# Rails Active Storage tables
end

User Flows

User Flows

Overview

This document describes the main user interaction flows for the chat application.


Flow 1: User Registration

┌──────────────────────────────────────────────────────────────┐
│ 1. Visit Homepage (not logged in)                            │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Click "Sign Up" button                                    │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Fill out registration form:                               │
│    - Username (3-20 characters)                              │
│    - Password (min 6 characters)                             │
│    - Password confirmation                                   │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. Submit form                                                │
└───────────────────────────────┬──────────────────────────────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
                    ↓                       ↓
        ┌─────────────────────┐  ┌─────────────────────┐
        │ Validation fails     │  │ Validation succeeds │
        │ Show errors          │  │ Create user         │
        │ Stay on form         │  │ Log in user         │
        └───────┬──────────────┘  └───────┬─────────────┘
                │                         │
                │                         ↓
                │              ┌─────────────────────┐
                │              │ Redirect to         │
                │              │ Room list page      │
                │              └─────────────────────┘
                │                         │
                └─────────────────────────┘

Success Criteria:
- User account created in database
- Password securely hashed with bcrypt
- User automatically logged in
- Session cookie set
- Redirected to room list


Flow 2: User Login

┌──────────────────────────────────────────────────────────────┐
│ 1. Visit Homepage or click "Sign In"                         │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Fill out login form:                                      │
│    - Username                                                 │
│    - Password                                                 │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Submit form                                                │
└───────────────────────────────┬──────────────────────────────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
                    ↓                       ↓
        ┌─────────────────────┐  ┌─────────────────────┐
        │ Invalid credentials  │  │ Valid credentials   │
        │ Show error message   │  │ Set session         │
        │ Stay on login page   │  │ Log in user         │
        └───────┬──────────────┘  └───────┬─────────────┘
                │                         │
                │                         ↓
                │              ┌─────────────────────┐
                │              │ Redirect to         │
                │              │ Room list page      │
                │              └─────────────────────┘
                │                         │
                └─────────────────────────┘

Success Criteria:
- User authenticated via bcrypt
- Session cookie created
- Redirected to room list
- Username displayed in navbar


Flow 3: Browse and Join Chat Room

┌──────────────────────────────────────────────────────────────┐
│ 1. User on Room List page (logged in)                        │
│    - See all available rooms                                 │
│    - See room names, descriptions                            │
│    - See active user counts                                  │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Click on a room to join                                   │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Navigate to Room Show page                                │
│    - Load message history (last 100 messages)                │
│    - Establish WebSocket connection                          │
│    - Subscribe to RoomChannel                                │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. User sees:                                                 │
│    - Room name and description                               │
│    - Message history                                          │
│    - List of online users                                    │
│    - Message input box                                       │
│    - "Leave Room" button                                     │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 5. Broadcast "user_joined" event                             │
│    - Notify all other users in room                          │
│    - Update online user list for everyone                    │
└──────────────────────────────────────────────────────────────┘

Success Criteria:
- WebSocket connection established
- User subscribed to room channel
- Message history loaded
- User appears in online list
- Join notification visible to others


Flow 4: Send and Receive Messages

┌──────────────────────────────────────────────────────────────┐
│ 1. User in chat room                                          │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Type message in input box                                  │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Click "Send" or press Enter                               │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. Client sends via Turbo form submit                        │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 5. Server (MessagesController#create):                       │
│    - Validate message                                         │
│    - Save to database                                         │
│    - Broadcast via Action Cable                              │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 6. Action Cable broadcasts to all subscribers                │
│    - Message sent via RoomChannel                            │
│    - All connected clients receive                           │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 7. Each client receives message:                             │
│    - Append message to chat window                           │
│    - Show username and timestamp                             │
│    - Auto-scroll to bottom                                   │
│    - Clear input box (for sender)                            │
└──────────────────────────────────────────────────────────────┘

Real-time Flow:

User A Server User B
│ │ │
│──── Send message ─────────→│ │
│ │ │
│ │──── Broadcast ─────────→│
│ │ │
│←─── Message appears ───────│ │
│ │ │
│ │ │
│ Message appears ───────────────────→│

Success Criteria:
- Message saved to database
- Message appears instantly for all users
- Sender's input cleared
- Auto-scroll to latest message
- Timestamp displayed
- Sender username shown


Flow 5: Leave Chat Room

┌──────────────────────────────────────────────────────────────┐
│ 1. User clicks "Leave Room" button                           │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. WebSocket unsubscribes from RoomChannel                   │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Broadcast "user_left" event                               │
│    - Notify remaining users                                  │
│    - Update online user list                                 │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. Navigate back to Room List                                │
└──────────────────────────────────────────────────────────────┘

Success Criteria:
- User unsubscribed from channel
- Leave notification sent to others
- User removed from online list
- Redirected to room list


Flow 6: Create New Chat Room

┌──────────────────────────────────────────────────────────────┐
│ 1. User on Room List page                                    │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Click "Create New Room" button                            │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Fill out new room form:                                   │
│    - Room name (required, 1-50 chars)                        │
│    - Description (optional, max 200 chars)                   │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. Submit form                                                │
└───────────────────────────────┬──────────────────────────────┘
                                │
                    ┌───────────┴───────────┐
                    │                       │
                    ↓                       ↓
        ┌─────────────────────┐  ┌─────────────────────┐
        │ Validation fails     │  │ Validation succeeds │
        │ (name taken/invalid) │  │ Create room         │
        │ Show errors          │  │ Save to database    │
        └───────┬──────────────┘  └───────┬─────────────┘
                │                         │
                │                         ↓
                │              ┌─────────────────────┐
                │              │ Redirect to new     │
                │              │ room's chat page    │
                │              │ User auto-joins     │
                │              └─────────────────────┘
                │                         │
                └─────────────────────────┘

Success Criteria:
- Room created in database
- Room appears in room list
- Creator automatically joins room
- Room accessible to all users


Flow 7: User Logout

┌──────────────────────────────────────────────────────────────┐
│ 1. User clicks "Sign Out" in navbar                          │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. Submit logout form (DELETE request)                       │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. Server destroys session                                   │
│    - Clear session cookie                                     │
│    - WebSocket disconnects                                   │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. Redirect to sign in page                                  │
└───────────────────────────────┬──────────────────────────────┘
                                │
                                ↓
┌──────────────────────────────────────────────────────────────┐
│ 5. Show flash message: "Signed out successfully"            │
└──────────────────────────────────────────────────────────────┘

Success Criteria:
- Session cleared
- User no longer authenticated
- WebSocket disconnected
- Redirected to login page


Flow 8: Real-time Presence Updates

User A (Browser)              Server                 User B (Browser)
      │                          │                           │
      │──── Join room ───────────→                           │
      │                          │                           │
      │                          │─── Broadcast joined ─────→│
      │                          │                           │
      │                          │                  Update user list
      │                          │                           │
      │                          │←──── User B joins ────────│
      │                          │                           │
      │←─── Broadcast joined ────│                           │
      │                          │                           │
Update user list                 │                           │
      │                          │                           │
      │                          │                           │
      │──── Leave room ──────────→                           │
      │                          │                           │
      │                          │─── Broadcast left ───────→│
      │                          │                           │
      │                          │                  Update user list

Success Criteria:
- Join events broadcast in real-time
- Leave events broadcast in real-time
- User list updated automatically
- No page refresh required


Error Handling Flows

Connection Lost Flow

1. User loses internet connection
2. WebSocket disconnects
3. Show "Connection lost" indicator
4. Attempt automatic reconnection
5. On success: "Connected" indicator
6. On failure: "Reconnecting..." with manual retry button

Message Send Failure Flow

1. User sends message
2. Network error or validation failure
3. Show error message
4. Keep message in input box
5. Allow user to retry

Invalid Session Flow

1. User's session expires
2. Attempt to access protected page
3. Redirect to sign in page
4. Show flash: "Please sign in to continue"
5. After login, redirect to originally requested page

Navigation Map

                         ┌──────────────────┐
                         │   Sign In Page   │
                         └────────┬─────────┘
                                  │
                    ┌─────────────┴─────────────┐
                    │                           │
                    ↓                           ↓
        ┌──────────────────┐        ┌──────────────────┐
        │  Sign Up Page    │        │  Room List Page  │
        └──────────────────┘        └────────┬─────────┘
                    │                        │
                    └────────────────┬───────┘
                                     │
                                     ↓
                        ┌─────────────────────────┐
                        │  Chat Room Page         │
                        │  (with WebSocket)       │
                        └─────────────────────────┘
                                     │
                                     │ Leave Room
                                     │
                                     ↓
                        ┌─────────────────────────┐
                        │  Back to Room List      │
                        └─────────────────────────┘

Mobile Considerations

Touch Interactions

  • Large tap targets (min 44x44px)
  • Swipe to scroll message history
  • Pull-to-refresh for room list
  • Touch-friendly send button

Mobile-specific Flows

  • Bottom-anchored message input (above keyboard)
  • Collapsible user list sidebar
  • Compact message layout
  • Auto-hide header on scroll

Accessibility Considerations

Keyboard Navigation

  • Tab through all interactive elements
  • Enter to send message
  • Escape to close modals
  • Arrow keys to navigate messages

Screen Reader Support

  • ARIA labels on buttons
  • Message announcements
  • Status indicators
  • Focus management

Agent Usage

Agent Usage Guide

Overview

This guide explains how AI agents (like Claude, GPT-4, Gemini) should approach implementing this Rails chat application blueprint.


Before Starting

1. Read Documents in Order

Read the blueprint documentation in this sequence:
1. README.md - Overview and quick start
2. 01_requirements.md - What needs to be built
3. 02_architecture.md - System design and components
4. 03datamodel.md - Database schema
5. 04userflows.md - User interactions
6. This file (05agentusage.md) - Implementation guidance
7. 06_tasks.md - Step-by-step tasks
8. 99agentnotes.md - Tips and gotchas

2. Confirm Environment

Verify the following are available:
- Ruby 3.2+ installed
- Rails 7.2+ gem installed
- PostgreSQL running (Docker container)
- Redis running (for Action Cable)
- Node.js 18+ (for JavaScript)
- VS Code editor

3. Understand the Context

  • This is a real-time chat application
  • Uses Action Cable for WebSocket communication
  • Focus on simplicity over feature completeness
  • Target: MVP in 4-6 hours

Implementation Strategy

Phase 1: Foundation (Task 1-2)

Goal: Get Rails app running with authentication

  1. Create new Rails app with proper configuration
  2. Set up PostgreSQL connection
  3. Configure Redis for Action Cable
  4. Add required gems
  5. Implement user authentication (bcrypt)
  6. Create sign up / sign in / sign out flows

Checkpoint: Users can register and log in

Phase 2: Core Models (Task 3-5)

Goal: Database and models ready

  1. Generate models (User, Room, Message)
  2. Set up associations
  3. Add validations
  4. Create seed data
  5. Test model relationships in console

Checkpoint: Can create rooms and messages in Rails console

Phase 3: Room Management (Task 6-7)

Goal: Users can browse and join rooms

  1. Create RoomsController
  2. Build room list view
  3. Build room show view (chat page)
  4. Add room creation form
  5. Style with Tailwind CSS

Checkpoint: Users can see and create rooms (no real-time yet)

Phase 4: Real-time Chat (Task 8-9)

Goal: WebSocket messaging works

  1. Generate RoomChannel
  2. Configure Action Cable
  3. Create Stimulus controller for chat
  4. Implement message broadcasting
  5. Test WebSocket connection

Checkpoint: Messages appear in real-time across browsers

Phase 5: User Presence (Task 10)

Goal: Show who's online

  1. Track user subscriptions
  2. Broadcast join/leave events
  3. Update UI with online users
  4. Test presence detection

Checkpoint: User list updates in real-time

Phase 6: Polish (Task 11-12)

Goal: Production-ready features

  1. Auto-scroll to latest message
  2. Empty states
  3. Loading indicators
  4. Error handling
  5. Mobile responsiveness
  6. Final testing

Checkpoint: App works smoothly on mobile and desktop


Working with Action Cable

Key Concepts

1. Channels are like WebSocket endpoints
```ruby

Think of channels as controllers for WebSocket

class RoomChannel < ApplicationCable::Channel
def subscribed
# User connects to a room
streamfrom "room#{params[:room_id]}"
end

def unsubscribed
# User disconnects
end
end
```

2. Broadcasting sends data to subscribers
```ruby

In your controller or model

ActionCable.server.broadcast(
"room#{roomid}",
message: "Hello!"
)
```

3. Client receives via JavaScript
javascript
// Stimulus controller
consumer.subscriptions.create(
{ channel: "RoomChannel", room_id: roomId },
{
received(data) {
// Handle incoming message
this.appendMessage(data)
}
}
)

Common Patterns

Pattern 1: Message Broadcasting
```ruby

After creating a message

message.broadcastappendto(
"room#{roomid}",
target: "messages",
partial: "messages/message"
)
```

Pattern 2: Presence Tracking
```ruby

Track connected users in Redis

def subscribed
currentuser.appearinroom(params[:roomid])
streamfrom "room#{params[:room_id]}"
end

def unsubscribed
currentuser.disappearfromroom(params[:roomid])
end
```

Pattern 3: Authorized Subscriptions
```ruby

Only logged-in users can subscribe

class ApplicationCable::Connection < ActionCable::Connection::Base
identifiedby :currentuser

def connect
self.currentuser = findverified_user
end

private

def findverifieduser
if verifieduser = User.findby(id: cookies.encrypted[:userid])
verified
user
else
rejectunauthorizedconnection
end
end
end
```


Code Organization

File Structure

app/
├── channels/
│   ├── application_cable/
│   │   ├── channel.rb
│   │   └── connection.rb
│   └── room_channel.rb
├── controllers/
│   ├── application_controller.rb
│   ├── rooms_controller.rb
│   ├── messages_controller.rb
│   ├── users_controller.rb
│   └── sessions_controller.rb
├── models/
│   ├── user.rb
│   ├── room.rb
│   └── message.rb
├── views/
│   ├── layouts/
│   │   └── application.html.erb
│   ├── rooms/
│   │   ├── index.html.erb
│   │   └── show.html.erb
│   ├── messages/
│   │   └── _message.html.erb
│   ├── users/
│   │   └── new.html.erb
│   └── sessions/
│       └── new.html.erb
└── javascript/
    ├── controllers/
    │   └── room_controller.js
    └── channels/
        ├── consumer.js
        └── room_channel.js

Controller Responsibilities

RoomsController
- index: List all rooms
- show: Display chat room
- create: Create new room

MessagesController
- create: Post new message (broadcasts via cable)

UsersController
- new: Sign up form
- create: Register user

SessionsController
- new: Sign in form
- create: Log in user
- destroy: Log out user


Testing Strategy

Manual Testing Checklist

Authentication:
- [ ] Can sign up with valid credentials
- [ ] Cannot sign up with invalid data
- [ ] Can sign in with correct password
- [ ] Cannot sign in with wrong password
- [ ] Can sign out
- [ ] Protected pages redirect when not logged in

Room Management:
- [ ] Can view room list
- [ ] Can create new room
- [ ] Cannot create room with duplicate name
- [ ] Can navigate to room

Real-time Messaging:
- [ ] Open room in 2+ browsers
- [ ] Send message in one browser
- [ ] Message appears in all browsers instantly
- [ ] Username and timestamp displayed
- [ ] Message persists after page refresh

User Presence:
- [ ] User appears in online list when joining
- [ ] User disappears when leaving
- [ ] Join notification visible to others
- [ ] Leave notification visible to others

UI/UX:
- [ ] Works on mobile screen sizes
- [ ] Auto-scrolls to latest message
- [ ] Input clears after sending
- [ ] Loading states visible
- [ ] Error messages displayed

Multi-window Testing

# Terminal 1: Start Rails
rails server

# Terminal 2: Start Redis
redis-server

# Browser 1: Sign in as Alice
# Browser 2: Sign in as Bob (incognito mode)
# Both join the same room and chat

Common Pitfalls

1. Redis Not Running

Problem: WebSocket connects but messages don't broadcast
Solution: Ensure Redis is running (redis-server)

2. Action Cable Not Configured

Problem: Cable connection fails
Solution: Check config/cable.yml has correct Redis URL

3. CSRF Token Issues

Problem: Form submissions fail
Solution: Ensure <%= csrf_meta_tags %> in layout

4. N+1 Queries

Problem: Slow message loading
Solution: Use .includes(:user) when loading messages

5. WebSocket Origin Errors

Problem: Cable rejected in production
Solution: Configure config.action_cable.allowed_request_origins

6. Memory Leaks

Problem: Multiple subscriptions created
Solution: Unsubscribe when leaving room


Debugging Tips

Check WebSocket Connection

// In browser console
App.cable.connection.webSocket.readyState
// 1 = OPEN, 0 = CONNECTING, 2 = CLOSING, 3 = CLOSED

Monitor Action Cable

# Rails console
ActionCable.server.connections.size
# Shows number of active connections

Redis Debugging

# Redis CLI
redis-cli
> KEYS *
> GET action_cable:...

Rails Console Testing

# Test broadcasting manually
ActionCable.server.broadcast("room_1", { message: "test" })

# Check subscriptions
ActionCable.server.pubsub.send(:redis_connection).pubsub("channels", "*")

Performance Optimization

Database

  • Add indexes on foreign keys
  • Eager load associations (.includes(:user))
  • Limit message history (last 100 messages)

Action Cable

  • Use Redis adapter (not async in production)
  • Set proper connection pool size
  • Configure timeout settings

Frontend

  • Debounce scroll events
  • Lazy load message history
  • Use Turbo for fast navigation

Deployment Considerations

Heroku

# Add Redis addon
heroku addons:create heroku-redis:mini

# Set Action Cable URL
heroku config:set ACTION_CABLE_URL=wss://yourapp.herokuapp.com/cable

Environment Variables

# .env file (use dotenv gem)
DATABASE_URL=postgresql://...
REDIS_URL=redis://localhost:6379/0
ACTION_CABLE_ALLOWED_ORIGINS=http://localhost:3000

Production Checklist

  • [ ] Redis addon provisioned
  • [ ] Action Cable URL configured
  • [ ] Allowed origins set
  • [ ] Database migrations run
  • [ ] Seed data loaded
  • [ ] WebSocket working across browsers

Agent Workflow

Step-by-Step Process

  1. Read all documentation (15 min)

    • Understand requirements and architecture
    • Note key features and constraints
  2. Set up Rails project (30 min)

    • Create app with proper config
    • Add gems
    • Configure database and Redis
  3. Implement authentication (45 min)

    • User model with bcrypt
    • Sign up / sign in / sign out
  4. Build core models (30 min)

    • Room and Message models
    • Associations and validations
    • Seed data
  5. Create room views (45 min)

    • Room list page
    • Room show page (chat)
    • Forms and navigation
  6. Add Action Cable (60 min)

    • Generate channel
    • Configure connection
    • Implement broadcasting
    • JavaScript subscription
  7. Implement presence (45 min)

    • Track join/leave
    • Update user list
    • Notifications
  8. Polish and test (45 min)

    • Auto-scroll
    • Mobile responsive
    • Error handling
    • Multi-browser testing

Total: 4-6 hours


Questions to Ask User

Before Starting

  • Do you have PostgreSQL running?
  • Do you have Redis installed?
  • Should we create a new Rails app or add to existing?
  • Any specific room features needed?

During Implementation

  • Does the authentication flow work for you?
  • Can you test in multiple browsers?
  • Is the UI responsive enough on mobile?

After Completion

  • Should we add direct messages?
  • Want file upload support?
  • Need typing indicators?

Success Criteria

Definition of Done

A task is complete when:
1. ✅ Code written and working
2. ✅ No console errors
3. ✅ Tested manually
4. ✅ UI looks good
5. ✅ Real-time features work across browsers

MVP Complete When

  • [ ] Users can sign up and log in
  • [ ] Users can create and join rooms
  • [ ] Messages broadcast in real-time
  • [ ] User presence visible
  • [ ] No major bugs
  • [ ] Mobile responsive
  • [ ] WebSocket reconnects gracefully

Next Steps After MVP

Phase 2 Features

  • Direct messaging (DM)
  • File uploads
  • Message editing/deletion
  • Typing indicators
  • Read receipts

Phase 3 Features

  • User profiles with avatars
  • Private rooms
  • Room moderation
  • Search messages
  • Email notifications

Scaling

  • Separate Action Cable servers
  • Redis cluster
  • Database read replicas
  • CDN for assets

Tasks

Implementation Tasks

Follow these tasks sequentially to build the Rails chat application with Action Cable.


Task 1: Project Setup

Create Rails Application

rails new chat-app \
  --database=postgresql \
  --css=tailwind \
  --javascript=importmap

Configure Database

Edit config/database.yml:
yaml
development:
adapter: postgresql
encoding: unicode
database: chat_app_development
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: postgres
password: postgres
host: database-server-dev1

Add Required Gems

Edit Gemfile:
```ruby
gem 'bcrypt', '~> 3.1.7' # Uncomment if commented
gem 'redis', '~> 5.0'

group :development, :test do
gem 'debug', platforms: %i[ mri windows ]
end
```

Run:
bash
bundle install

Configure Redis for Action Cable

Edit config/cable.yml:
```yaml
development:
adapter: redis
url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>

test:
adapter: test

production:
adapter: redis
url: <%= ENV.fetch("REDISURL") { "redis://localhost:6379/1" } %>
channel
prefix: chatappproduction
```

Setup Database

rails db:create
rails db:migrate

Test Server

./bin/dev  # Or rails server

Visit http://localhost:3000 - you should see Rails welcome page.

✅ Checkpoint: Rails app running with PostgreSQL and Redis configured


Task 2: User Authentication

Generate User Model

rails generate model User username:string password_digest:string
rails db:migrate

Configure User Model

Edit app/models/user.rb:
```ruby
class User < ApplicationRecord
hassecurepassword

validates :username, presence: true,
uniqueness: true,
length: { in: 3..20 }
validates :password, length: { minimum: 6 }, allow_nil: true
end
```

Create Sessions Controller

rails generate controller Sessions new create destroy

Edit app/controllers/sessions_controller.rb:
```ruby
class SessionsController < ApplicationController
def new
# Sign in form
end

def create
user = User.findby(username: params[:username])
if user&.authenticate(params[:password])
session[:user
id] = user.id
redirectto roomspath, notice: "Signed in successfully!"
else
flash.now[:alert] = "Invalid username or password"
render :new, status: :unprocessable_entity
end
end

def destroy
session[:userid] = nil
redirect
to signinpath, notice: "Signed out successfully!"
end
end
```

Create Users Controller

rails generate controller Users new create

Edit app/controllers/users_controller.rb:
```ruby
class UsersController < ApplicationController
def new
@user = User.new
end

def create
@user = User.new(userparams)
if @user.save
session[:user
id] = @user.id
redirectto roomspath, notice: "Account created successfully!"
else
render :new, status: :unprocessable_entity
end
end

private

def userparams
params.require(:user).permit(:username, :password, :password
confirmation)
end
end
```

Add Authentication Helpers

Edit app/controllers/application_controller.rb:
```ruby
class ApplicationController < ActionController::Base
helpermethod :currentuser, :logged_in?

def currentuser
@current
user ||= User.findby(id: session[:userid]) if session[:user_id]
end

def loggedin?
current
user.present?
end

def requirelogin
unless logged
in?
flash[:alert] = "You must be logged in to access this page"
redirectto signin_path
end
end
end
```

Create Routes

Edit config/routes.rb:
```ruby
Rails.application.routes.draw do
root "sessions#new"

get "signin", to: "sessions#new"
post "sign
in", to: "sessions#create"
delete "sign_out", to: "sessions#destroy"

get "signup", to: "users#new"
post "sign
up", to: "users#create"

resources :rooms, only: [:index, :show, :create] do
resources :messages, only: [:create]
end
end
```

Create Sign In View

Create app/views/sessions/new.html.erb:
```erb

Sign In

<%= formwith url: signinpath, method: :post, local: true do |f| %>

<%= f.label :username, class: "block mb-2" %>
<%= f.text
field :username, required: true,
class: "w-full px-4 py-2 border rounded" %>

<div class="mb-4">
  <%= f.label :password, class: "block mb-2" %>
  <%= f.password_field :password, required: true, 
      class: "w-full px-4 py-2 border rounded" %>
</div>

<%= f.submit "Sign In", class: "w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" %>

<% end %>

Don't have an account? <%= linkto "Sign Up", signup_path, class: "text-blue-500" %>


```

Create Sign Up View

Create app/views/users/new.html.erb:
```erb

Sign Up

<%= formwith model: @user, url: signuppath, local: true do |f| %>
<% if @user.errors.any? %>


<% @user.errors.full
messages.each do |message| %>
<%= message %>
<% end %>


<% end %>

<div class="mb-4">
  <%= f.label :username, class: "block mb-2" %>
  <%= f.text_field :username, required: true, 
      class: "w-full px-4 py-2 border rounded" %>
</div>

<div class="mb-4">
  <%= f.label :password, class: "block mb-2" %>
  <%= f.password_field :password, required: true, 
      class: "w-full px-4 py-2 border rounded" %>
</div>

<div class="mb-4">
  <%= f.label :password_confirmation, class: "block mb-2" %>
  <%= f.password_field :password_confirmation, required: true, 
      class: "w-full px-4 py-2 border rounded" %>
</div>

<%= f.submit "Sign Up", class: "w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" %>

<% end %>

Already have an account? <%= linkto "Sign In", signin_path, class: "text-blue-500" %>


```

Update Application Layout

Edit app/views/layouts/application.html.erb:
```erb
<!DOCTYPE html>


ChatApp

<%= csrfmetatags %>
<%= cspmetatag %>
<%= stylesheetlinktag "tailwind", "inter-font", "data-turbo-track": "reload" %>
<%= stylesheetlinktag "application", "data-turbo-track": "reload" %>
<%= javascriptimportmaptags %>

<% if loggedin? %>


💬 Chat App

👤 <%= current
user.username %>
<%= buttonto "Sign Out", signout_path, method: :delete,
class: "bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600" %>



<% end %>

<% if flash[:notice] %>
  <div class="container mx-auto px-4 mb-4">
    <div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded">
      <%= flash[:notice] %>
    </div>
  </div>
<% end %>

<% if flash[:alert] %>
  <div class="container mx-auto px-4 mb-4">
    <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded">
      <%= flash[:alert] %>
    </div>
  </div>
<% end %>

<%= yield %>

```

✅ Checkpoint: Users can sign up and sign in


Task 3: Create Room and Message Models

Generate Room Model

rails generate model Room name:string description:text

Generate Message Model

rails generate model Message content:text user:references room:references

Add Index to Messages

Edit the migration file for messages and add:
ruby
add_index :messages, [:room_id, :created_at]

Run Migrations

rails db:migrate

Configure Room Model

Edit app/models/room.rb:
```ruby
class Room < ApplicationRecord
has_many :messages, dependent: :destroy

validates :name, presence: true,
uniqueness: true,
length: { in: 1..50 }
validates :description, length: { maximum: 200 }

def recentmessages(limit = 100)
messages.includes(:user).order(created
at: :desc).limit(limit).reverse
end
end
```

Configure Message Model

Edit app/models/message.rb:
```ruby
class Message < ApplicationRecord
belongsto :user
belongs
to :room

validates :content, presence: true, length: { in: 1..1000 }

aftercreatecommit do
broadcastappendto(
"room#{roomid}",
target: "messages",
partial: "messages/message",
locals: { message: self }
)
end
end
```

Update User Model

Edit app/models/user.rb and add:
ruby
has_many :messages, dependent: :destroy

✅ Checkpoint: Models created with associations


Task 4: Create Seed Data

Edit db/seeds.rb:
```ruby

Clear existing data

Message.destroyall
Room.destroy
all
User.destroy_all

Create users

alice = User.create!(username: "alice", password: "password123", passwordconfirmation: "password123")
bob = User.create!(username: "bob", password: "password123", password
confirmation: "password123")

puts "Created users: alice, bob (password: password123)"

Create rooms

general = Room.create!(name: "General", description: "General discussion room")
random = Room.create!(name: "Random", description: "Random topics and off-topic chat")
rails_help = Room.create!(name: "Rails Help", description: "Get help with Ruby on Rails")

puts "Created rooms: General, Random, Rails Help"

Create sample messages

Message.create!(user: alice, room: general, content: "Welcome to the General chat room!")
Message.create!(user: bob, room: general, content: "Thanks! Happy to be here.")
Message.create!(user: alice, room: rails_help, content: "Anyone have experience with Action Cable?")

puts "Created sample messages"
puts "✅ Seed data complete!"
```

Run seeds:
bash
rails db:seed

✅ Checkpoint: Database has sample data


Task 5: Create Rooms Controller and Views

Generate Rooms Controller

rails generate controller Rooms index show create

Configure Rooms Controller

Edit app/controllers/rooms_controller.rb:
```ruby
class RoomsController < ApplicationController
beforeaction :requirelogin

def index
@rooms = Room.all.order(created_at: :desc)
@room = Room.new
end

def show
@room = Room.find(params[:id])
@messages = @room.recent_messages
@message = Message.new
end

def create
@room = Room.new(roomparams)
if @room.save
redirect
to @room, notice: "Room created successfully!"
else
@rooms = Room.all
render :index, status: :unprocessable_entity
end
end

private

def room_params
params.require(:room).permit(:name, :description)
end
end
```

Create Room Index View

Create app/views/rooms/index.html.erb:
```erb


<!-- Room List -->

Chat Rooms

  <div class="grid gap-4">
    <% @rooms.each do |room| %>
      <%= link_to room, class: "block bg-white p-6 rounded-lg shadow hover:shadow-lg transition" do %>
        <h3 class="text-xl font-bold mb-2"><%= room.name %></h3>
        <p class="text-gray-600"><%= room.description %></p>
        <p class="text-sm text-gray-500 mt-2">
          <%= room.messages.count %> messages
        </p>
      <% end %>
    <% end %>
  </div>
</div>

<!-- Create Room Form -->
<div>
  <h2 class="text-2xl font-bold mb-4">Create Room</h2>

  <%= form_with model: @room, url: rooms_path, local: true, class: "bg-white p-6 rounded-lg shadow" do |f| %>
    <% if @room.errors.any? %>
      <div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
        <ul>
          <% @room.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
        </ul>
      </div>
    <% end %>

    <div class="mb-4">
      <%= f.label :name, class: "block mb-2 font-bold" %>
      <%= f.text_field :name, required: true, 
          class: "w-full px-4 py-2 border rounded" %>
    </div>

    <div class="mb-4">
      <%= f.label :description, class: "block mb-2 font-bold" %>
      <%= f.text_area :description, rows: 3, 
          class: "w-full px-4 py-2 border rounded" %>
    </div>

    <%= f.submit "Create Room", class: "w-full bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" %>
  <% end %>
</div>

```

Create Room Show View

Create app/views/rooms/show.html.erb:
```erb


<!-- Room Header -->


<%= @room.name %>
<%= @room.description %>

<%= linkto "← Back to Rooms", roomspath,
class: "text-blue-500 hover:text-blue-700" %>

<!-- Messages Container -->
<div id="messages" class="p-4 h-96 overflow-y-auto space-y-3">
  <%= render @messages %>
</div>

<!-- Message Form -->
<div class="border-t p-4">
  <%= form_with model: [@room, @message], 
                local: false,
                data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" } do |f| %>
    <div class="flex space-x-2">
      <%= f.text_area :content, 
          rows: 2,
          placeholder: "Type your message...",
          required: true,
          class: "flex-1 px-4 py-2 border rounded resize-none" %>
      <%= f.submit "Send", 
          class: "bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600" %>
    </div>
  <% end %>
</div>

```

Create Message Partial

Create app/views/messages/_message.html.erb:
erb
<div id="<%= dom_id(message) %>" class="flex items-start space-x-3">
<div class="flex-1 bg-gray-100 rounded-lg p-3">
<div class="flex items-baseline space-x-2 mb-1">
<span class="font-bold text-sm"><%= message.user.username %></span>
<span class="text-xs text-gray-500">
<%= message.created_at.strftime("%I:%M %p") %>
</span>
</div>
<p class="text-gray-800"><%= message.content %></p>
</div>
</div>

✅ Checkpoint: Can view rooms and see message history


Task 6: Create Messages Controller

Create app/controllers/messages_controller.rb:
```ruby
class MessagesController < ApplicationController
beforeaction :requirelogin

def create
@room = Room.find(params[:roomid])
@message = @room.messages.build(message
params)
@message.user = current_user

if @message.save
  # Message will be broadcast automatically via after_create_commit
  head :ok
else
  render json: { errors: @message.errors.full_messages }, 
         status: :unprocessable_entity
end

end

private

def message_params
params.require(:message).permit(:content)
end
end
```

✅ Checkpoint: Can post messages (not real-time yet)


Task 7: Configure Action Cable

Create Room Channel

rails generate channel Room

Configure Room Channel

Edit app/channels/room_channel.rb:
```ruby
class RoomChannel < ApplicationCable::Channel
def subscribed
room = Room.find(params[:roomid])
stream
for room
end

def unsubscribed
# Cleanup when channel is unsubscribed
end
end
```

Configure Cable Connection

Edit app/channels/application_cable/connection.rb:
```ruby
module ApplicationCable
class Connection < ActionCable::Connection::Base
identifiedby :currentuser

def connect
  self.current_user = find_verified_user
end

private

def find_verified_user
  if verified_user = User.find_by(id: cookies.encrypted[:user_id])
    verified_user
  else
    reject_unauthorized_connection
  end
end

end
end
```

Create Stimulus Controller

Create app/javascript/controllers/room_controller.js:
```javascript
import { Controller } from "@hotwired/stimulus"
import { createConsumer } from "@rails/actioncable"

export default class extends Controller {
static values = { roomId: Number }

connect() {
this.channel = createConsumer().subscriptions.create(
{ channel: "RoomChannel", roomid: this.roomIdValue },
{
connected: this.
connected.bind(this),
disconnected: this.disconnected.bind(this),
received: this.
received.bind(this)
}
)

this.scrollToBottom()

}

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

_connected() {
console.log("Connected to room channel")
}

_disconnected() {
console.log("Disconnected from room channel")
}

_received(data) {
// Turbo Stream will handle the append
this.scrollToBottom()
}

scrollToBottom() {
const messages = document.getElementById("messages")
if (messages) {
messages.scrollTop = messages.scrollHeight
}
}
}
```

Create Reset Form Controller

Create app/javascript/controllers/reset_form_controller.js:
```javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
reset() {
this.element.reset()
}
}
```

Update Room Show View

Edit app/views/rooms/show.html.erb and add data-controller:
erb
<div class="container mx-auto px-4 max-w-4xl"
data-controller="room"
data-room-room-id-value="<%= @room.id %>">
<!-- rest of the code -->
</div>

✅ Checkpoint: Messages broadcast in real-time via WebSocket


Task 8: Test Real-time Features

Start Services

# Terminal 1: Start Redis
redis-server

# Terminal 2: Start Rails
./bin/dev

Test in Multiple Browsers

  1. Open http://localhost:3000 in Chrome
  2. Sign in as alice
  3. Join "General" room
  4. Open http://localhost:3000 in Firefox (or Incognito)
  5. Sign in as bob
  6. Join "General" room
  7. Send messages from both browsers
  8. Messages should appear instantly in both windows

✅ Checkpoint: Real-time messaging works across browsers


Task 9: Add Auto-scroll

The auto-scroll is already implemented in the Stimulus controller from Task 7.

Test:
1. Send multiple messages to fill the chat window
2. New messages should automatically scroll to bottom
3. If manually scrolled up, new messages still append (no auto-scroll)

✅ Checkpoint: Auto-scroll working


Task 10: Mobile Responsiveness

Tailwind CSS handles most responsiveness. Test on mobile:
1. Open in Chrome DevTools mobile view
2. Test room list
3. Test chat interface
4. Ensure input is accessible above keyboard

Optional improvements in app/views/rooms/show.html.erb:
erb
<!-- Make chat area taller on mobile -->
<div id="messages" class="p-4 h-[60vh] md:h-96 overflow-y-auto space-y-3">

✅ Checkpoint: Works on mobile devices


Task 11: Error Handling

Add Connection Status Indicator

Update app/javascript/controllers/room_controller.js:
```javascript
_connected() {
console.log("Connected to room channel")
this.showStatus("Connected", "green")
}

_disconnected() {
console.log("Disconnected from room channel")
this.showStatus("Disconnected", "red")
}

showStatus(message, color) {
// Optional: Add status indicator to UI
}
```

Handle Failed Message Send

Already handled in messages_controller.rb with validation errors.

✅ Checkpoint: Basic error handling in place


Task 12: Final Testing

Testing Checklist

  • [ ] Sign up with valid data
  • [ ] Sign up with invalid data (see errors)
  • [ ] Sign in with correct password
  • [ ] Sign in with wrong password
  • [ ] View room list
  • [ ] Create new room
  • [ ] Join existing room
  • [ ] See message history
  • [ ] Send message (appears for sender)
  • [ ] Open second browser, send message (appears in first browser)
  • [ ] Messages persist after refresh
  • [ ] Sign out works
  • [ ] Mobile responsive
  • [ ] No console errors

Performance Check

  • [ ] Page loads in < 2 seconds
  • [ ] Messages appear in < 100ms
  • [ ] No N+1 queries (check logs)
  • [ ] Redis connected

✅ Checkpoint: All features working, MVP complete!


Task 13: Optional Enhancements

User Presence

Track online users in each room:

  1. Add Stimulus controller to track presence
  2. Broadcast join/leave events
  3. Display online user list
  4. Show join/leave notifications

Typing Indicators

Show "User is typing..." indicator:

  1. Debounce keystrokes
  2. Broadcast typing event
  3. Display indicator in UI
  4. Clear after 3 seconds

Message Timestamps

Better formatting:

# In message partial
<%= time_ago_in_words(message.created_at) %> ago

Deployment

Heroku Deployment

heroku create your-chat-app
heroku addons:create heroku-postgresql:mini
heroku addons:create heroku-redis:mini
git push heroku main
heroku run rails db:migrate
heroku run rails db:seed
heroku open

Environment Variables

heroku config:set ACTION_CABLE_ALLOWED_ORIGINS=https://your-app.herokuapp.com

Maintenance Tasks

Database Cleanup

# Remove old messages (optional cron job)
Message.where("created_at < ?", 30.days.ago).destroy_all

Monitor Connections

# Rails console
ActionCable.server.connections.count

Success Metrics

After completing all tasks:
- ✅ Users can register and authenticate
- ✅ Users can create and join rooms
- ✅ Messages broadcast in real-time
- ✅ WebSocket connection stable
- ✅ UI responsive on mobile
- ✅ No major bugs or errors
- ✅ Code organized and readable
- ✅ Ready for deployment

🎉 Congratulations! Your Rails chat app with Action Cable is complete!

Notes

Agent Notes

Implementation Tips and Gotchas

This document contains practical notes, tips, and common issues encountered during implementation.


Action Cable Gotchas

1. Redis Must Be Running

Problem: WebSocket connects but messages don't broadcast.

Symptom:

Connection successful but no messages appearing

Solution:
```bash

Make sure Redis is running

redis-server

Or in Docker

docker run -d -p 6379:6379 redis:7-alpine
```

Verify:
```bash
redis-cli ping

Should return: PONG


---

### 2. Cookie-based Authentication
**Problem:** WebSocket connection rejected.

**Symptom:**

An unauthorized connection attempt was rejected
```

Solution: Ensure session cookie is set after login:
```ruby

In SessionsController#create

session[:user_id] = user.id
```

Important: Action Cable uses the same session cookie as Rails, so authentication "just works" if session is properly set.


3. Turbo Streams vs Manual Broadcasting

Problem: Confused about when to use which.

Best Practice:
```ruby

✅ Use Turbo Streams (automatic)

class Message < ApplicationRecord
aftercreatecommit do
broadcastappendto(
"room#{roomid}",
target: "messages",
partial: "messages/message"
)
end
end

❌ Don't manually broadcast HTML

ActionCable.server.broadcast("room1", { html: rendermessage })
```

Why: Turbo Streams handle DOM updates automatically.


4. N+1 Queries in Real-time

Problem: Slow message rendering.

Bad:
ruby
def recent_messages
messages.order(created_at: :desc).limit(100)
end

Good:
ruby
def recent_messages
messages.includes(:user).order(created_at: :desc).limit(100)
end

Verify in logs:
```

Bad (N+1)

SELECT * FROM messages WHERE room_id = 1
SELECT * FROM users WHERE id = 1
SELECT * FROM users WHERE id = 2
...

Good (2 queries)

SELECT * FROM messages WHERE room_id = 1
SELECT * FROM users WHERE id IN (1, 2, 3, ...)
```


Stimulus Controller Tips

1. Auto-scroll on New Message

Implementation:
```javascript
_received(data) {
// Turbo Stream handles DOM update
// Just scroll after a short delay
setTimeout(() => this.scrollToBottom(), 50)
}

scrollToBottom() {
const container = document.getElementById("messages")
container.scrollTop = container.scrollHeight
}
```

Why the delay: Turbo Stream needs time to append DOM before scrolling.


2. Clear Input After Send

Problem: Input box doesn't clear after sending message.

Solution: Use Stimulus controller:
```javascript
// resetformcontroller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
reset(event) {
if (event.detail.success) {
this.element.reset()
}
}
}
```

In form:
erb
<%= form_with model: [@room, @message],
data: {
controller: "reset-form",
action: "turbo:submit-end->reset-form#reset"
} do |f| %>


3. Unsubscribe on Disconnect

Problem: Multiple subscriptions created when navigating.

Solution: Always unsubscribe:
javascript
disconnect() {
if (this.channel) {
this.channel.unsubscribe()
}
}


Database Tips

1. Composite Index for Messages

Why:
sql
-- Common query pattern
SELECT * FROM messages
WHERE room_id = 1
ORDER BY created_at DESC
LIMIT 100;

Migration:
ruby
add_index :messages, [:room_id, :created_at]

Result: 10x faster queries on large datasets.


2. Dependent Destroy

Important: Set up cascading deletes:
```ruby
class Room < ApplicationRecord
has_many :messages, dependent: :destroy
end

class User < ApplicationRecord
has_many :messages, dependent: :destroy
end
```

Why: Prevents orphaned records.


Tailwind CSS Tips

1. Scrollable Chat Container

<div class="h-96 overflow-y-auto">
  <!-- messages -->
</div>

Mobile-friendly:
erb
<div class="h-[60vh] md:h-96 overflow-y-auto">


2. Message Bubble Layout

<div class="flex items-start space-x-3">
  <div class="flex-1 bg-gray-100 rounded-lg p-3">
    <!-- message content -->
  </div>
</div>

Why flex-1: Allows message to expand but not overflow.


Testing Tips

1. Multi-browser Testing

# Regular browser
open http://localhost:3000

# Incognito/Private
# Chrome: Cmd+Shift+N (Mac) / Ctrl+Shift+N (Windows)
# Firefox: Cmd+Shift+P (Mac) / Ctrl+Shift+P (Windows)

Pro tip: Use different user agents to see different usernames.


2. WebSocket Debugging

Browser Console:
```javascript
// Check connection status
App.cable.connection.webSocket.readyState
// 0 = CONNECTING, 1 = OPEN, 2 = CLOSING, 3 = CLOSED

// See active subscriptions
App.cable.subscriptions.subscriptions
```

Rails Console:
```ruby

Check active connections

ActionCable.server.connections.count

Broadcast manually

ActionCable.server.broadcast("room_1", { test: "message" })
```


3. Redis Debugging

redis-cli

# List all keys
KEYS *

# Monitor in real-time
MONITOR

# Get value
GET action_cable:...

Performance Optimization

1. Limit Message History

Don't load all messages:
```ruby

Bad

@messages = @room.messages.includes(:user)

Good

@messages = @room.recent_messages(100)
```


2. Redis Connection Pool

For production:
```yaml

config/cable.yml

production:
adapter: redis
url: <%= ENV['REDISURL'] %>
channel
prefix: chatappproduction
poolsize: <%= ENV.fetch("RAILSMAX_THREADS") { 5 } %>
```


3. Compress Large Payloads

For images/files (future feature):
```ruby

Use Active Storage with CDN

Don't broadcast binary data via Action Cable


---

## Security Notes

### 1. Sanitize User Input
**Already handled by Rails:**
```erb
<!-- Automatic HTML escaping -->
<p><%= message.content %></p>

For rich text (future):
```ruby
gem 'sanitize'

Sanitize before save

beforesave :sanitizecontent

def sanitize_content
self.content = Sanitize.fragment(content, Sanitize::Config::BASIC)
end
```


2. Rate Limiting (Optional)

Prevent spam:
```ruby

Gemfile

gem 'rack-attack'

config/initializers/rack_attack.rb

Rack::Attack.throttle("messages/ip", limit: 10, period: 60) do |req|
req.ip if req.path.start_with?('/rooms') && req.post?
end
```


3. Channel Authorization

Already implemented:
```ruby

app/channels/application_cable/connection.rb

def connect
self.currentuser = findverified_user
end

def findverifieduser
if verifieduser = User.findby(id: cookies.encrypted[:userid])
verified
user
else
rejectunauthorizedconnection
end
end
```

Result: Only authenticated users can connect.


Deployment Notes

Heroku-specific

1. Redis Addon:
bash
heroku addons:create heroku-redis:mini

2. Action Cable Configuration:
```ruby

config/environments/production.rb

config.actioncable.url = ENV.fetch("ACTIONCABLEURL") {
"wss://#{ENV['HEROKU
APPNAME']}.herokuapp.com/cable"
}
config.action
cable.allowedrequestorigins = [
"https://#{ENV['HEROKUAPPNAME']}.herokuapp.com"
]
```

3. Environment Variables:
bash
heroku config:set ACTION_CABLE_URL=wss://your-app.herokuapp.com/cable


Database Performance

1. Connection Pool:
```yaml

config/database.yml

production:
pool: <%= ENV.fetch("RAILSMAXTHREADS") { 5 } %>
```

2. Query Timeout:
```ruby

config/database.yml

production:
variables:
statement_timeout: 5000 # 5 seconds
```


Common Error Messages

"Redis connection refused"

Cause: Redis not running

Fix:
bash
redis-server


"Couldn't find Room with 'id'="

Cause: Missing room parameter or invalid ID

Fix: Check routes and form submission


"An unauthorized connection attempt was rejected"

Cause: User not logged in

Fix: Ensure session[:user_id] is set


"ActionController::InvalidAuthenticityToken"

Cause: Missing CSRF token

Fix: Ensure <%= csrf_meta_tags %> in layout


Future Enhancements

1. Direct Messages

Schema:
```ruby
createtable :conversations do |t|
t.references :sender, foreign
key: { totable: :users }
t.references :receiver, foreign
key: { to_table: :users }
t.timestamps
end

createtable :directmessages do |t|
t.references :conversation
t.references :user
t.text :content
t.timestamps
end
```


2. Typing Indicators

Channel method:
ruby
def typing
broadcast_to(
room,
type: "typing",
user: current_user.username
)
end

Client debounce:
javascript
typing = debounce(() => {
this.channel.perform("typing")
}, 300)


3. Read Receipts

Schema:
ruby
add_column :messages, :read_by, :jsonb, default: []

Track:
ruby
def mark_as_read(user_id)
update(read_by: read_by + [user_id])
end


4. Message Editing

Schema:
ruby
add_column :messages, :edited_at, :datetime

Broadcast update:
ruby
after_update_commit do
broadcast_replace_to(
"room_#{room_id}",
target: self,
partial: "messages/message"
)
end


Best Practices Summary

Do's ✅

  • Use Turbo Streams for DOM updates
  • Eager load associations (.includes(:user))
  • Limit message history (100 messages max)
  • Unsubscribe when leaving channel
  • Test in multiple browsers
  • Use Redis in production
  • Set up proper indexes

Don'ts ❌

  • Don't manually manipulate DOM in channel callbacks
  • Don't forget to start Redis
  • Don't broadcast large payloads
  • Don't skip authentication checks
  • Don't load all messages at once
  • Don't forget CSRF tokens
  • Don't ignore N+1 queries

Debugging Workflow

  1. Check Rails logs:
    bash
    tail -f log/development.log

  2. Check Redis:
    bash
    redis-cli ping

  3. Check browser console:

    • Look for WebSocket connection
    • Check for JavaScript errors
  4. Test in Rails console:
    ```ruby

    Test models

    room = Room.first
    room.messages.create!(user: User.first, content: "test")

# Test broadcasting
ActionCable.server.broadcast("room_1", { message: "test" })
```

  1. Monitor Action Cable: bash # Logs show connections and subscriptions Started GET "/cable" for 127.0.0.1 Successfully upgraded to WebSocket

Resources

Official Docs

Useful Gems

  • redis - Redis client
  • bcrypt - Password hashing
  • tailwindcss-rails - Styling
  • turbo-rails - Hotwire Turbo
  • stimulus-rails - Stimulus JS

Final Checklist

Before considering the project complete:

  • [ ] Redis running and connected
  • [ ] Users can sign up/in/out
  • [ ] Rooms can be created
  • [ ] Messages send in real-time
  • [ ] Messages persist in database
  • [ ] UI works on mobile
  • [ ] No N+1 queries
  • [ ] No console errors
  • [ ] WebSocket reconnects automatically
  • [ ] Code is clean and organized
  • [ ] Ready for deployment

Support & Troubleshooting

If you encounter issues:

  1. Check this document first
  2. Review error messages carefully
  3. Test each component individually
  4. Use Rails console for debugging
  5. Check Redis and PostgreSQL are running
  6. Verify browser WebSocket support

Remember: Most Action Cable issues are caused by:
- Redis not running
- Missing authentication
- N+1 queries
- Subscription not unsubscribing


Good luck with your implementation! 🚀