Introduction
If you’ve ever wanted your Rails app to feel alive, you should check out Action Cable. It’s Rails’ built-in way of doing WebSockets, which basically means your server and the browser can stay connected and chat in real time. No more page refreshes, no clunky polling — updates just show up instantly.
In this guide, we’ll build a fun little demo: a live traffic counter. When someone lands on your site, we’ll log that visit in the database, bump some counters in memory, and broadcast the updated numbers to everyone online. Close the tab? The online user count drops immediately. It’s simple, but it shows the real-time magic that Action Cable brings to Rails 7.
Step 1: The Visit Model
First up, let’s make a model to log visits.
bin/rails g model Visit session_id:string ip:string user_agent:string
bin/rails db:migrate
# app/models/visit.rb
class Visit < ApplicationRecord
end
Step 2: Creating a Custom Connection
Rails already gives you two base classes in app/channels/application_cable/
:
- channel.rb
→ the base class for all your channels.
- connection.rb
→ the base class for all your WebSocket connections.
We don’t normally edit those. Instead, we extend them by creating our own connection class inside app/channels/
. This keeps the defaults clean and puts our logic in a clear place.
# app/channels/traffic_connection.rb
module ApplicationCable
class TrafficConnection < Connection
identified_by :session_id, :ip, :user_agent
def connect
self.session_id = request.session.id
self.ip = request.ip
self.user_agent = request.user_agent
end
end
end
Step 3: Counters in Memory
We’ll use atomic counters to make things fast. At boot, we grab the total visits from the DB, and start online users at zero.
# config/initializers/traffic_counter.rb
$online_users = Concurrent::AtomicFixnum.new(0)
$total_visits = Concurrent::AtomicFixnum.new(Visit.count)
Step 4: The Channel
Here’s where the fun happens — log the visit, update counters, and broadcast.
# app/channels/traffic_channel.rb
class TrafficChannel < ApplicationCable::Channel
def subscribed
# log visit
Visit.create!(session_id: connection.session_id,
ip: connection.ip,
user_agent: connection.user_agent)
# update counters
$online_users.increment
$total_visits.increment
stream_from "traffic_channel"
broadcast_counts
end
def unsubscribed
$online_users.decrement
broadcast_counts
end
private
def broadcast_counts
ActionCable.server.broadcast("traffic_channel", {
online: $online_users.value,
total: $total_visits.value
})
end
end
Step 5: The JavaScript Side
When you run bin/rails generate channel Traffic
, Rails creates two files: the Ruby channel and the JavaScript subscription file. By default, the JS file imports consumer.js
, which Rails also set up for you when the app was generated.
// app/javascript/channels/traffic_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("TrafficChannel", {
received(data) {
document.querySelector("#online-count").textContent = data.online
document.querySelector("#total-count").textContent = data.total
}
})
The consumer.js
file is generated by Rails when you create a new app. It sets up the global WebSocket connection to /cable
and looks like this:
// app/javascript/channels/consumer.js
import { createConsumer } from "@rails/actioncable"
export default createConsumer()
Step 6: The View
<h1>🚦 Live Traffic</h1>
<p>Currently online: <span id="online-count">0</span></p>
<p>Total visits: <span id="total-count"><%= Visit.count %></span></p>
Step 7: Routes
# config/routes.rb
Rails.application.routes.draw do
root "home#index"
mount ActionCable.server => "/cable"
end
Step 8: Try It Out
Run your app with bin/dev
. Open your site in a few tabs. The online users counter ticks up with each one, and drops when you close them. Total visits just keeps going up. Super satisfying to watch!
Notes for Production
- Multiple Puma workers? Use Redis for counters — memory won’t sync across processes.
- Want to track actual users instead of sessions? Add current_user
in the connection.
- Storing visits in the DB means you can run analytics later, like daily traffic or browser breakdowns.
And that’s it — a real-time live traffic counter in Rails 7 with Action Cable. In under 100 lines of code, you’ve got instant updates and a taste of what WebSockets can do. This same setup works for notifications, dashboards, and all kinds of real-time features. Once you’ve tried it, you’ll want to sprinkle it everywhere.