Sitemap
Natsuko

Natsuko

I write tech-related articles for organizing my thoughts and keeping a record of them.

Ruby on Rails + Hotwire

15 min readSep 23, 2026

--

1. The Overall Flow: Route → Controller → Action → View

2. Routes

3. Controllers and Actions

4. Views

5. Hotwire

1. The Overall Flow: Route → Controller → Action → View

1.1 The Big Picture

Browser
│
│ HTTP request
│ e.g. GET /users
↓
config/routes.rb
│
│ GET /users → UsersController#index
↓
UsersController
│
│ index action
↓
app/views/users/index.html.erb
│
│ ERB generates HTML
↓
HTML sent to the browser
Route
↓
decides which controller action handles the request

Controller
↓
receives the request and coordinates what should happen
Action
↓
a method inside the controller that handles a particular request
View
↓
generates the HTML that the browser receives
               config/routes.rb
│
┌──────────────┴──────────────┐
↓ ↓
Routes / URL mapping Route helper methods
↓ ↓
Controller#Action ERB
↓ ↓
View Generated HTML

1.2 URL vs. Route

/users
GET  /users
POST /users
Route
= HTTP method + URL pattern → Controller#Action
GET /users
↓
UsersController#index
URL:
/users

Route:
GET /users → UsersController#index

1.3 Controller and Action

class UsersController < ApplicationController
end
class UsersController < ApplicationController
def index
end

def show
end
end
UsersController
├── index action
└── show action
GET /users
↓
UsersController#index
Controller#Action
UsersController#index

1.4 How a Request Becomes HTML

GET /users
GET /users
↓
routes.rb
↓
UsersController#index
↓
app/views/users/index.html.erb
↓
HTML
↓
Browser
UsersController#index

2. Routes

2.1 config/routes.rb

config/routes.rb
Rails.application.routes.draw do
resources :users
end

2.2 resources :users

resources :users
index
show
new
create
edit
update
destroy
Press enter or click to view image in full size
index
GET /users → UsersController#index

2.3 The Seven Conventional Routes

INDEX

GET /users
GET /users
↓
UsersController#index
↓
app/views/users/index.html.erb

SHOW

GET /users/123
GET /users/123
↓
UsersController#show

NEW

GET /users/new
GET /users/new
↓
UsersController#new
↓
app/views/users/new.html.erb

CREATE

POST /users
User fills out new-user form
↓
POST /users
↓
UsersController#create
↓
User.new(...)
↓
@user.save
↓
database
GET  /users/new
= display the form

POST /users
= process the submitted form and create the record

EDIT

GET /users/123/edit
GET /users/123/edit
↓
UsersController#edit
↓
app/views/users/edit.html.erb

UPDATE

PATCH /users/123
GET /users/123/edit
= display the edit form

PATCH /users/123
= submit changes and update the record

DESTROY

DELETE /users/123
UsersController#destroy

2.4 Understanding index, show, new, create, edit, update, and destroy

index
"Show me the collection."

show
"Show me one existing resource."
new
"Give me a form for creating a new resource."
create
"Create the resource using this submitted data."
edit
"Give me a form for editing an existing resource."
update
"Update the existing resource using this submitted data."
destroy
"Delete the existing resource."
new    → show a creation form
create → actually create

edit → show an editing form
update → actually update

2.5 Route Helper Methods

users_path
/users
user_path(@user)
/users/123
new_user_path
# => /users/new

edit_user_path(@user)
# => /users/123/edit
<%= link_to "Create a new user", new_user_path %>
<a href="/users/new">Create a new user</a>
new_user_path
/users/new
<a href="/users/new">
Route helper
↓
URL
↓
HTML
users_url
https://example.com/users

2.6 collection and member

collection

resources :users do
collection do
get :search
end
end
GET /users/search
↓
UsersController#search
search_users_path
/users/search
collection
= the group as a whole

member

resources :users do
member do
get :profile
end
end
GET /users/:id/profile
↓
UsersController#profile
/users/123/profile
profile_user_path(@user)
member
= one specific resource
collection
GET /users/search
↓
the collection of users

member
GET /users/123/profile
↓
one specific user

2.7 path:, only:, and except:

path:

resources :users, path: "members"
/members
/members/123
/members/new
users_path
user_path(@user)

only:

resources :users, only: [:index, :show]
GET /users
GET /users/:id

except:

resources :users, except: [:destroy]

3. Controllers and Actions

3.1 What Is a Controller?

class UsersController < ApplicationController
end

3.2 What Is an Action?

class UsersController < ApplicationController
def index
end

def show
end
end
index
show
GET /users
↓
UsersController#index

3.3 The Seven Conventional Controller Actions

class UsersController < ApplicationController
def index
end

def show
end
def new
end
def create
end
def edit
end
def update
end
def destroy
end
end
resources :users

3.4 How Controller Actions Connect to Views

def index
end
UsersController
app/views/users/index.html.erb
UsersController#show
↓
app/views/users/show.html.erb

UsersController#new
↓
app/views/users/new.html.erb
UsersController#edit
↓
app/views/users/edit.html.erb
Controller:
UsersController

↓
View directory:
app/views/users/
↓
Action:
index
↓
View:
index.html.erb
"render index.html.erb"
GET /users
↓
UsersController#index
↓
Rails convention
↓
app/views/users/index.html.erb

3.5 new vs. create

def new
@user = User.new
end
GET /users/new
↓
UsersController#new
↓
@user = User.new
↓
new.html.erb
↓
form
POST /users
↓
UsersController#create
def create
@user = User.new(user_params)

if @user.save
redirect_to @user
else
render :new
end
end

@user = User.new(user_params)

User.new(...)

if @user.save

@user.save
true
false

redirect_to @user

redirect_to @user
redirect_to user_path(@user)
/users/123
GET /users/123
↓
UsersController#show

render :new

render :new
app/views/users/new.html.erb

Get Natsuko’s stories in your inbox

Join Medium for free to get updates from this writer.

render
= render a view now

redirect_to
= tell the browser to make another request

3.6 edit vs. update

def edit
@user = User.find(params[:id])
end
GET /users/123/edit
↓
UsersController#edit
↓
edit.html.erb
↓
user submits changes
↓
PATCH /users/123
↓
UsersController#update
def update
@user = User.find(params[:id])

if @user.update(user_params)
redirect_to @user
else
render :edit
end
end

params[:id]

/users/123
params[:id]
123

User.find(params[:id])

@user.update(user_params)

redirect_to @user

render :edit

3.7 render vs. redirect_to

render
redirect_to
render :new
GET /users/new
redirect_to @user
GET /users/123

3.8 Database Operations and Active Record

@user = User.find(params[:id])
@user.save
@user.update(user_params)
HTTP request
↓
Controller
↓
Model / Active Record
↓
Database

3.9 Strong Parameters: params, require, and permit

def user_params
params.require(:user).permit(:name, :email)
end
class UsersController < ApplicationController
def create
@user = User.new(user_params)

if @user.save
redirect_to @user
else
render :new
end
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end

What is params?

{
user: {
name: "Alice",
email: "alice@example.com"
}
}
params

What does require(:user) do?

params.require(:user)
params
{
user: {
name: "Alice",
email: "alice@example.com"
}
}
params.require(:user)
{
name: "Alice",
email: "alice@example.com"
}

What does permit(:name, :email) do?

permit(:name, :email)
{
user: {
name: "Alice",
email: "alice@example.com",
admin: true
}
}
params.require(:user).permit(:name, :email)
{
name: "Alice",
email: "alice@example.com"
}
HTTP request
↓
params
↓
require(:user)
↓
select the user parameter
↓
permit(:name, :email)
↓
allow only specified attributes
↓
User.new(user_params)
permit(:name, :email)

4. Views

4.1 What Is a View?

app/views/
app/views/users/
app/views/users/index.html.erb
app/views/users/show.html.erb
app/views/users/new.html.erb
app/views/users/edit.html.erb

4.2 ERB

<h1>Users</h1>
<% @users.each do |user| %>
<p><%= user.name %></p>
<% end %>
<%= ... %>
<% ... %>

4.3 How Rails Finds index.html.erb

GET /users
UsersController#index
app/views/users/index.html.erb
UsersController
↓
app/views/users/

index action
↓
index.html.erb
show action
↓
show.html.erb
new action
↓
new.html.erb
edit action
↓
edit.html.erb
render "app/views/users/index.html.erb"

4.4 Using Route Helpers in ERB

<%= link_to "Users", users_path %>
users_path
/users
<a href="/users">Users</a>
<%= link_to "Edit", edit_user_path(@user) %>
/users/123/edit
users_path
= Ruby helper method

/users
= URL
<a href="/users">
= HTML

4.5 From ERB to HTML

Controller provides data
↓
ERB template uses the data
↓
Rails evaluates the Ruby
↓
HTML is generated
↓
Browser receives HTML
<h1><%= @user.name %></h1>
@user.name
<h1>Alice</h1>

5. Hotwire

5.1 What Is Hotwire?

Turbo
Stimulus

5.2 Turbo

Turbo Drive
Turbo Frames
Turbo Streams
Turbo Drive
→ page-level navigation

Turbo Frames
→ navigation/update within a specific region
Turbo Streams
→ specific DOM operations

5.3 Stimulus

click
input
submit
change
Turbo
= navigation and HTML updates

Stimulus
= JavaScript behavior and interaction

5.4 Turbo Drive

<%= link_to "Users", users_path %>
User clicks link
↓
GET /users
↓
Server returns HTML
↓
Turbo Drive updates the current document
data-turbo="false"

5.5 Turbo Frames

<%= turbo_frame_tag "user_2" do %>
<p>Bob</p>
<%= link_to "Edit", edit_user_path(2) %>
<% end %>
<turbo-frame id="user_2">
<p>Bob</p>
<a href="/users/2/edit">Edit</a>
</turbo-frame>
Whole page
┌──────────────────────────────┐
│ Header │
│ │
│ ┌──────────────────────────┐ │
│ │ turbo-frame │ │
│ │ │ │
│ │ User #2 │ │
│ │ Edit │ │
│ │ │ │
│ └──────────────────────────┘ │
│ │
│ Footer │
└──────────────────────────────┘

5.6 Turbo Streams

<turbo-stream action="remove" target="user_2">
</turbo-stream>
Find the element with id="user_2"
↓
Remove it from the DOM
append
prepend
replace
update
remove
before
after
<turbo-stream action="append" target="comments">
<template>
<p>Hello</p>
</template>
</turbo-stream>
Find #comments
↓
Append the supplied HTML inside it

5.7 What Triggers a Turbo Stream?

<%= button_to "Delete",
user_path(@user),
method: :delete %>
User clicks "Delete"
↓
DELETE /users/2
↓
UsersController#destroy
↓
@user.destroy
↓
Server returns a Turbo Stream response
↓
Turbo processes the stream
↓
Target element is removed
<%= turbo_stream.remove dom_id(@user) %>
<turbo-stream action="remove" target="user_2">
</turbo-stream>

append works the same way

<turbo-stream action="append" target="comments">
<template>
<p>Hello</p>
</template>
</turbo-stream>
User submits "Add comment" form
↓
POST /comments
↓
CommentsController#create
↓
Comment is saved
↓
Server returns Turbo Stream
↓
Turbo processes:

action="append"
target="comments"
↓
New comment is appended
Button / form / link / browser event
↓
causes a request or other interaction
↓
server or client produces a Turbo Stream
↓
Turbo processes the stream instruction
↓
DOM changes

5.8 Turbo Drive vs. Turbo Frames vs. Turbo Streams

Turbo Drive

Link
↓
new page HTML
↓
Turbo updates the document

Turbo Frames

Page
↓
specific <turbo-frame>
↓
only that region changes

Turbo Streams

Turbo Stream
↓
append / prepend / replace / update / remove / before / after
Turbo Drive
= page-level navigation
Turbo Frame
= defined region
Turbo Stream
= DOM operation/instruction

Final Example: Putting Everything Together

Rails.application.routes.draw do
resources :users do
collection do
get :search
end
end
end

The seven standard routes

config/routes.rb
│
┌─────────────────┴─────────────────┐
│ │
↓ ↓
HTTP request Route helpers
GET /users users_path
│ │
↓ ↓
UsersController#index "/users"
│ │
↓ ↓
app/views/users/index.html.erb ERB template
│ │
└──────────────┬────────────────────┘
↓
Generated HTML
↓
Browser
Browser
│
│ User interaction
↓
HTTP request
│
↓
routes.rb
│
↓
Controller#Action
│
├── Model / Active Record
│ ↓
│ Database
│
↓
HTML / Turbo response
│
├── Turbo Drive
│ → page-level navigation
│
├── Turbo Frame
│ → update a specific region
│
└── Turbo Stream
→ perform a specific DOM operation
│
↓
Browser DOM
URL
= address such as /users/123
Route
= HTTP method + URL pattern → Controller#Action
Controller
= class responsible for handling requests
Action
= method inside a controller
View
= HTML-generating template
ERB
= HTML with embedded Ruby
Route helper
= Ruby method that generates a URL
Model / Active Record
= application layer that handles data and database persistence
Turbo Drive
= page-level navigation
Turbo Frame
= a defined region of the page
Turbo Stream
= an instruction for modifying the DOM
HTTP request
↓
Route
↓
Controller#Action
↓
Model / Active Record (when data is needed)
↓
View
↓
HTML
↓
Browser
UsersController#index
↓
app/views/users/index.html.erb
users_path
↓
/users
↓
<a href="/users">...</a>

--

Natsuko
Natsuko

Written by Natsuko

I write tech-related articles for organizing my thoughts and keeping a record of them.