Turbo can largely simplify our front-end needs to achieve a single-page application feel. If you have ever wondered how to do a single attribute in-place update with Turbo, this post is for you.
I’ll assume you have Turbo (with turbo-rails
gem) installed, and you already have a classic model CRUD done. If you don’t, just generate a standard scaffold. I’ll use the User
model and the name
attribute, but it can be anything.
At this point, you might have a controller for the model looking like this:
class UsersController < ApplicationController
before_action :set_user, only: %i[ show edit update destroy ]
...
# GET /users/1/edit
def edit
end
# PATCH/PUT /users/1 or /users/1.json
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to user_path(@user), notice: "User was successfully updated." }
format.json { render :show, status: :ok, location: user_path(@user) }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
# Only allow a list of trusted parameters through.
def user_params
params.require(:user).permit(:name)
end
end
You should also have all the standard views that go with it, namely views/users/show.html.erb
, that we’ll modify for in-place editing of the user’s name.
We make a specific page for this change to support editing a specific attribute (here a name
).
The controller change is easy. We add edit_name
method next to your original edit
:
class UsersController < ApplicationController
before_action :set_user, only: %i[ show edit edit_name update destroy password_reset ]
# GET /users/1/edit
def edit
end
# GET /users/1/edit_name
def edit_name
end
# PATCH/PUT /users/1 or /users/1.json
def update
respond_to do |format|
if @user.update(user_params)
format.html { redirect_to user_path(@user), notice: "User was successfully updated." }
format.json { render :show, status: :ok, location: user_path(@user) }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: @user.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_user
@user = User.find(params[:id])
end
# Only allow a list of trusted parameters through.
def user_params
params.require(:user).permit(:name)
end
end
Notice that there is no need to change how update
works, it can do the job for all the attributes at once.
And let’s not forget to make the new path accessible with a change to routes.rb
file:
Rails.application.routes.draw do
...
resources :users do
member do
get 'edit_name'
end
end
# Defines the root path route ("/")
root "application#index"
end
Now that we have a new route and controller method to render the form for the name change, we implement the views.
We’ll add a standard view for the edit_name
action (views/users/edit_name.html.erb
):
<%= form_with model: @user, url: user_path(@user) do |form| %>
<%= form.text_field :name %>
<%= form.submit "Save" %>
<% end %>
And then wrap it with turbo_frame_tag
call:
<%= turbo_frame_tag :user_name do %>
<%= form_with model: @user, url: user_path(@user) do |form| %>
<%= form.text_field :name %>
<%= form.submit "Save" %>
<% end %>
<% end %>
Wrapping everything in turbo_frame_tag
gives this form a unique identifier and determines the area that gets swapped later.
Notice that we don’t need a specific model ID for turbo_frame_tag
(like the examples leveraging dom_id
) as we will swap the content on the model’s show
page where other user entries don’t exist.
Once prepared, we make another turbo_frame_tag
on the show
page with the same ID. This tells Turbo that it can swap it with the frame we defined in the previous step:
...
<%= turbo_frame_tag :user_name do %>
Name: <%= link_to @user.name, edit_name_user_path(@user) %>
<% end %>
...
A link_to
pointing to the specific path for editing the name will trigger the action, and Turbo does the rest!
Get Test Driving Rails while it's in prerelease.