Free sample · Chapter 1

Introduction and Setup

A complete chapter from Realtime Rails. No teaser fragments and no second signup form.

The time has come! You are going to build a real-time web application from scratch with Rails 8. No React, no Vue, no frontend framework at all. Just Rails, the Hotwire stack that ships with it, and a little bit of Stimulus when you need client-side behavior.

First things first, though. We need to set up your development environment and generate the application.

Why Real-Time, Why Rails?

For years, "real-time" on the web meant polling. Your browser would fire off AJAX requests every few seconds, asking "anything new?" and the server would usually say no. It works, and plenty of apps still do, but users can still feel the delay. In a chat app, if someone sends you a message and it takes 5 or 10 seconds to show up, that is a problem.

WebSockets solved the underlying problem. Instead of the browser pestering the server, you open a persistent connection, and the server pushes data to the client whenever something happens. But if you have ever tried to wire up WebSockets by hand, you know how much plumbing is involved. Connection management, serialization, reconnection logic, and then you still have to figure out how to take whatever data comes back and actually update the DOM with it.

Rails 8 handles all of that for you. Turbo sends HTML fragments (not JSON) over the wire, Action Cable manages the WebSocket connections, and Stimulus gives you just enough client-side JavaScript when you need it. The Solid trifecta (Solid Queue, Solid Cache, Solid Cable) means you do not even need Redis to get started. Everything runs on SQLite.

Luckily for us and all Rails developers, rails new in Rails 8 is basically the real-time starter kit. In a JavaScript-heavy stack, you would be piecing together a frontend framework, an API layer, a state management library, a WebSocket client, probably Redis for pub/sub, and a bunch of glue code to hold it all together. Rails 8 ships with all of that out of the box.

The Hotwire Philosophy

Hotwire stands for "HTML Over The Wire." The idea is that your server renders HTML and sends it to the browser, same as Rails has always done, but now it can also push HTML fragments over a WebSocket connection to update parts of a page without a full reload.

Hotwire is really three tools working together:

Stimulus is the companion library for client-side behavior. It is not a framework, and you will not be building components or managing state with it. Stimulus gives you small controllers that attach to DOM elements and respond to events, things like toggling a dropdown or copying text to the clipboard.

Action Cable is the WebSocket layer underneath Turbo Streams. When your code broadcasts a Turbo Stream update, Action Cable is what actually delivers it. You can also use Action Cable directly for things that Turbo Streams cannot express, like a typing indicator or live cursor positions.

We will go over all of these in detail throughout the book. For now, just know that they layer on top of each other and each one builds on what the one below it does. Drive is the foundation, Frames and Streams get more targeted from there, and Stimulus is for the small bits of client-side JavaScript that the other tools do not cover.

Introducing Switchboard

The app we are building is called Switchboard. It is a team chat application, think Slack but much simpler, with rooms (you might call them channels), direct messages, and live presence indicators. Chat is a great fit for real-time learning because everything about it is inherently live. Messages should appear the moment they are sent, typing indicators should appear while someone is composing, and user presence (online, away, offline) should update automatically. If any of those things require a page refresh, something is wrong.

Over the course of this book, you will build:

Each chapter adds a feature, and by the end, Switchboard will be a fully functional real-time chat app.

Prerequisites and Environment Setup

Let's get your machine ready. You need Ruby, the Rails gem, SQLite (which your system almost certainly already has), and a text editor or IDE. That is it. Rails 8 uses Importmaps for JavaScript, so there is no Node.js, no Yarn, no npm, and no JS build step to deal with.

Installing Ruby

You need Ruby 3.4 or newer. Throughout the book, we will use 3.4.9, the latest patch release at the time of writing.

I recommend mise for managing Ruby versions. It handles Ruby, Node, Python, and just about everything else with one tool. I switched to it from asdf a while back and have not looked back.

# Install mise (macOS/Linux)
curl https://mise.run | sh

# Install Ruby
mise use --global ruby@3.4.9

# Verify
ruby --version

If you already have a preferred version manager (rbenv, asdf, chruby), use that instead. The important thing is that ruby --version reports 3.4.x or higher.

Installing Rails 8

With Ruby in place, install the Rails gem:

gem install rails
rails --version

You should see something like Rails 8.1.x. If you see a version older than 8.0, make sure your Ruby is on the correct version and try gem install rails again.

It may not be all smooth sailing when installing Rails. I have personally run into OpenSSL issues on a clean system, and the native extensions for the sqlite3 gem can sometimes be finicky depending on your OS version. Predicting the issues you may hit across different machines, operating systems, and library versions is impossible. If you run into something, Google the error message plus your operating system. You will usually find an answer in the first StackOverflow link.

SQLite

Rails 8 defaults to SQLite for development (and honestly, for production too, if your app fits the scale). On macOS, SQLite comes preinstalled. On Ubuntu/Debian:

sudo apt-get install libsqlite3-dev

You likely already have it. Run sqlite3 --version to check.

Generating the Switchboard App

Alright! Let's create the application.

rails new switchboard --css=tailwind
cd switchboard

Rails 8 uses Propshaft as the default asset pipeline (Sprockets is gone) and Importmaps as the default JavaScript approach. The only flag we need is --css=tailwind to pull in the tailwindcss-rails gem. Everything else comes automatically: Propshaft, Importmaps, Solid Queue, Solid Cache, Solid Cable.

Take a minute to look at what Rails generated. If you are coming from Rails 7, a few things have changed.

config/database.yml defaults to SQLite for development, test, and production. Nothing extra to install or configure.

Gemfile includes solid_queue, solid_cache, and solid_cable out of the box. These are the Solid trifecta, replacing the Redis dependencies you might remember from older Rails versions. Background jobs, caching, and WebSocket pub/sub now run on SQLite adapters, so if you have ever spent time getting Redis running in development just to work on a feature, that is no longer necessary.

If you were to open config/cable.yml, you would see it defaults to the Solid Cable adapter. WebSockets work without any external services running.

config/deploy.yml is a Kamal 2 deployment configuration. We will not touch deployment in this book, but Rails 8 ships with a production deployment story built in, which is nice.

The Procfile.dev has two entries: the Rails server and the Tailwind CSS watcher. We will use bin/dev instead of bin/rails server throughout the book so that Tailwind's watcher is always running and your styles compile as you make changes.

Setting Up the Database

bin/rails db:prepare

This creates the SQLite database files and runs any pending migrations. We just generated the app, so there are no migrations yet, but it is a good habit to run db:prepare early. It is idempotent, meaning you can run it whenever you want without worrying about it doing something twice.

Running the App for the First Time

bin/dev

Open http://localhost:3000, and you should see the Rails boot screen with the Rails logo and your Ruby and Rails version numbers. Nothing exciting yet, but everything is wired up.

Easy peasy!

Hit Ctrl+C to stop the server when you are done looking around.

Seeding Users

Switchboard is a chat app, so we need users. Now, building a full authentication system with sign-up pages, password resets, and email confirmation would eat a whole chapter, and you would not learn anything about real-time features from it. That is not what this book is about.

Instead, we are going to take a shortcut. We will create a simple User model, seed a few users, and wire up a current_user helper that pulls the active user from the session. In the next chapter, we will add a dev-only dropdown that lets you switch between users with a single click. For now, let's just get the data in place.

The User Model

Generate the model:

bin/rails generate model User name:string email:string
bin/rails db:migrate

That gives you a users table with name and email columns plus the standard created_at and updated_at timestamps.

Open the model and add some validations:

app/models/user.rb
class User < ApplicationRecord
  validates :name, presence: true
  validates :email, presence: true, uniqueness: true
end

Nothing fancy. We just want to make sure every user has a name and a unique email address.

Seed Data

Open db/seeds.rb:

db/seeds.rb
users = [
  { name: "Miles", email: "miles@example.com" },
  { name: "Gwen", email: "gwen@example.com" },
  { name: "Peter", email: "peter@example.com" }
]

users.each do |attrs|
  User.find_or_create_by!(email: attrs[:email]) do |user|
    user.name = attrs[:name]
  end
end

puts "Seeded #{User.count} users"

We use find_or_create_by! so you can re-run seeds without creating duplicates. Run it:

bin/rails db:seed

You should see Seeded 3 users in the output. You can verify in the console:

bin/rails console
User.pluck(:name)
# => ["Miles", "Gwen", "Peter"]

Good. Three users, ready to chat. Type exit to leave the console.

The current_user Helper

We need a way for controllers and views to know which user is "logged in." Since we don't have real authentication, we will use the session to store a user ID and fall back to Miles if nothing is set.

Create a concern:

app/controllers/concerns/set_current_user.rb
module SetCurrentUser
  extend ActiveSupport::Concern

  included do
    helper_method :current_user
    before_action :set_current_user
  end

  private

  def current_user
    @current_user
  end

  def set_current_user
    @current_user = User.find_by(id: session[:user_id]) || User.first
  end
end

Include it in ApplicationController:

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SetCurrentUser
end

Now every request has a current_user. If there is a user ID in the session, it loads that user; otherwise, it falls back to User.first, which is Miles if you ran the seeds in the order we wrote them. The helper_method call at the top makes current_user available in your views, too, which we will need once we start building the layout.

We will add a user-switcher dropdown in the next chapter so you can switch users without having to touch the console. For now, you are always Miles.

A Tour of the Realtime Stack

Before we start building features, let's take a look at what Turbo Drive is already doing in your app. You did not install it separately. It shipped with rails new.

Start the server if it is not running:

bin/dev

Seeing Turbo Drive in Action

Let's create a quick scratch page.

Generate a controller:

bin/rails generate controller Pages home about

Two actions, two views. Now open config/routes.rb:

config/routes.rb
Rails.application.routes.draw do
  get "home", to: "pages#home"
  get "about", to: "pages#about"

  root "pages#home"
end

Add some content to the views:

app/views/pages/home.html.erb
<div class="p-8">
  <h1 class="text-3xl font-bold mb-4">Switchboard</h1>
  <p class="mb-4">Welcome to Switchboard, your real-time team chat app.</p>
  <%= link_to "About this app", about_path, class: "text-blue-600 underline" %>
</div>
app/views/pages/about.html.erb
<div class="p-8">
  <h1 class="text-3xl font-bold mb-4">About Switchboard</h1>
  <p class="mb-4">Switchboard is the demo application for Realtime Rails.</p>
  <%= link_to "Back to home", root_path, class: "text-blue-600 underline" %>
</div>

Visit http://localhost:3000 in your browser. Click the "About this app" link. Click "Back to home." Go back and forth a few times.

You will notice the page transitions are fast. There is no full-page flash of white between pages, and the URL bar updates normally, but the browser is not actually doing a traditional page load. If you open your dev tools and watch the Network tab while you click around, you will see fetch requests instead of full document loads.

That is Turbo Drive doing its thing. It intercepted those link clicks, fetched the page via fetch() behind the scenes, and swapped the <body> content into the existing document without doing a full reload. You did not write any JavaScript or configure anything. It is just on by default.

A little side note, though: Turbo Drive isn't doing anything magical with your data. It is navigation optimization, nothing more. Your server still renders complete HTML pages just like it always has, and your controllers do not know Turbo exists. Every Rails app gets this behavior for free the moment the turbo-rails gem is included, which rails new does automatically.

We will cover Turbo Frames and Turbo Streams in later chapters for more targeted updates, but you just saw the base layer working without any configuration.

Version Control

Now would be a good time to commit your work. We will follow a commit-per-chapter workflow throughout the book, so you always have a snapshot to come back to if something breaks.

git add -A
git commit -m "Chapter 1: Initial Switchboard application with users and Turbo Drive demo"

If you run into issues with git, make sure you are inside the switchboard directory. Rails generates a .gitignore that handles the common exclusions (log files, tmp, SQLite databases in development), so you should not have to worry about committing files you do not want.

What did you learn in this chapter?

That’s the full first chapter.
Continue with the complete book when you’re ready.
Get Realtime Rails