Ruby on Rails 7: Hotwire ta Turbo dlia Reaktyvnykh Dodatkiv
Povnyi posibnyk z Hotwire ta Turbo v Rails 7. Stvorennia reaktyvnykh dodatkiv bez JavaScript z Turbo Drive, Frames ta Streams.

Rails 7 zrobyla revoliutsiiu u veb-rozrobtsi, intehruvavshi Hotwire za zamovchuvannia. Tsei stek dozvoliaie stvoriuvaty vysoce reaktyvni dodatky bez napysannia zhodnoi riadka vlasnoho kodu JavaScript. Turbo Drive, Turbo Frames ta Turbo Streams zaminiuiut tradytsiini pidkhody SPA filosofiieiu "HTML cherez drit".
Hotwire drastychno zmenshuie skladnist frontendu. Ne potribno React chy Vue dlia dynamichnykh interfejsiv: server nadsilaie hotovyi do vykorystannia HTML, a Turbo avtomatychno opratsoovuie onovlennia DOM.
Rozuminnia Arkhitektury Hotwire
Hotwire skladaietsia z triokh komplementarnykh tekhnolohii, shcho pratsiuiut razom dlia zabezpechennia plavnoho dosvidu korystuvacha bez typovoi skladnosti JavaScript-freimvorkiv.
Turbo Drive pryskoruie navigatsiiu, perekhopliuiuchy klyky na posylannia ta vidpravky form. Zamist perenavantazhennia vsiiei storinky, zaminiuietsia lyshe vmist <body>, zberigaiuchy kontekst JavaScript ta CSS.
Turbo Frames rozbyvaiut storinky na nezalezhni sektsii. Kozhen frejm mozhe onovliuvatysia okremo, shcho dozvoliaie tsilespriamovani vzaiemodii bez vplyvu na reshtu storinky.
Turbo Streams zabezpechuiut onovlennia v realnomu chasi cherez WebSocket abo u vidpovid na HTTP-zapyty. Visim CRUD-dii dostupni dlia deklaratyvnoi manipuliatsii DOM.
# Gemfile
# Installing Turbo Rails (included by default in Rails 7+)
gem 'turbo-rails'Dlia isnuiuchykh proektiv instaliatsiia vymahaie lyshe odniiei komandy.
# installation.sh
# Installing Turbo in an existing Rails project
rails turbo:install
# Verify the JavaScript import is present
cat app/javascript/application.js
# Should contain: import "@hotwired/turbo-rails"Pochatkova Konfiguratsiia Turbo Drive
Turbo Drive uvimkneno za zamovchuvannia v Rails 7. Usia navigatsiia avtomatychno staie "turbo" bez dodatkovi konfiguratsiiu. Povedinku mozhna nalashtuvannia cherez data-atrybuty.
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<meta name="turbo-cache-control" content="no-preview">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<!-- Progress bar during navigation -->
<div class="turbo-progress-bar"></div>
<%= yield %>
</body>
</html>Turbo Drive mozhna vymknuty dlia konkretnykh posylan chy form za potreby.
<!-- app/views/pages/examples.html.erb -->
<!-- Link with Turbo enabled (default) -->
<%= link_to "Dashboard", dashboard_path %>
<!-- Disable Turbo for a specific link -->
<%= link_to "Download PDF", export_path, data: { turbo: false } %>
<!-- Disable Turbo for a form (file upload for example) -->
<%= form_with model: @document, data: { turbo: false } do |f| %>
<%= f.file_field :attachment %>
<%= f.submit "Upload" %>
<% end %>
<!-- Turbo with confirmation -->
<%= link_to "Delete", item_path(@item),
data: { turbo_method: :delete, turbo_confirm: "Are you sure?" } %>Turbo Drive keshuie vidvidani storinky. Atrybut data-turbo-track="reload" na resursakh zmushue povne perenavantazhennia, yakshcho faily CSS/JS zminiatsia.
Turbo Frames dlia Tsilespriamovanykh Onovlen
Turbo Frames vyznachaiut zony storinky, shcho onovliuiutsia nezalezhno. Kozhen frejm maie unikalnyi identyfikator i reahuie lyshe na vidpovidi, shcho mistyat vidpovidnyi frejm.
<!-- app/views/messages/index.html.erb -->
<h1>Messages</h1>
<!-- Frame for the message list -->
<turbo-frame id="messages_list">
<div id="messages">
<%= render @messages %>
</div>
<%= link_to "Load more", messages_path(page: @next_page) %>
</turbo-frame>
<!-- Frame for the creation form -->
<turbo-frame id="new_message">
<%= render "form", message: Message.new %>
</turbo-frame>Vidpovid servera povianna mistyty frejm z tym samym identyfikatorom, shchob onovlennia pratsiiuvalo.
<!-- app/views/messages/_message.html.erb -->
<!-- Partial for an individual message -->
<turbo-frame id="<%= dom_id(message) %>">
<div class="message">
<p><%= message.content %></p>
<span class="metadata">
By <%= message.author.name %> - <%= time_ago_in_words(message.created_at) %>
</span>
<!-- These links stay within the frame -->
<%= link_to "Edit", edit_message_path(message) %>
<%= button_to "Delete", message_path(message), method: :delete %>
</div>
</turbo-frame>Lienyve Zavantazhennia z Turbo Frames
Frejmy mozhut zavantazhuvaty svii vmist asynkhronno za dopomohoiu atrybutu src. Pochatkovyi vmist vidobrazhaietsia pid chas zavantazhennia.
<!-- app/views/dashboard/show.html.erb -->
<h1>Dashboard</h1>
<!-- Statistics loaded asynchronously -->
<turbo-frame id="stats" src="<%= dashboard_stats_path %>">
<div class="loading-placeholder">
<p>Loading statistics...</p>
</div>
</turbo-frame>
<!-- Notifications loaded when visible (lazy loading) -->
<turbo-frame id="notifications"
src="<%= notifications_path %>"
loading="lazy">
<p>Loading notifications...</p>
</turbo-frame>
<!-- Recent activity with periodic refresh -->
<turbo-frame id="recent_activity"
src="<%= activity_path %>"
data-controller="auto-refresh"
data-auto-refresh-interval-value="30000">
<%= render "activity/placeholder" %>
</turbo-frame>Kontroler vidpovidaie podanniiam, shcho mistyat lyshe zapytuvany frejm.
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
def stats
@user_count = User.count
@message_count = Message.count
@active_today = User.where("last_seen_at > ?", 24.hours.ago).count
# Partial rendering for the frame
render partial: "dashboard/stats"
end
end<!-- app/views/dashboard/_stats.html.erb -->
<turbo-frame id="stats">
<div class="stats-grid">
<div class="stat-card">
<h3><%= @user_count %></h3>
<p>Users</p>
</div>
<div class="stat-card">
<h3><%= @message_count %></h3>
<p>Messages</p>
</div>
<div class="stat-card">
<h3><%= @active_today %></h3>
<p>Active today</p>
</div>
</div>
</turbo-frame>Готовий до співбесід з Ruby on Rails?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Turbo Streams dlia Onovlen v Realnomu Chasi
Turbo Streams proponuie visim dii dlia manipuliatsii DOM: append, prepend, replace, update, remove, before, after ta morph. Tsi dii mozhut buti zapushcheni cherez vidpovid HTTP abo WebSocket.
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def create
@message = current_user.messages.build(message_params)
respond_to do |format|
if @message.save
# Turbo Stream response to add the message
format.turbo_stream do
render turbo_stream: [
turbo_stream.prepend("messages", @message),
turbo_stream.update("message_count", partial: "messages/count"),
turbo_stream.replace("new_message", partial: "messages/form",
locals: { message: Message.new })
]
end
format.html { redirect_to messages_path, notice: "Message created" }
else
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"new_message",
partial: "messages/form",
locals: { message: @message }
)
end
format.html { render :new, status: :unprocessable_entity }
end
end
end
def destroy
@message = Message.find(params[:id])
@message.destroy
respond_to do |format|
# Removal with animation
format.turbo_stream { render turbo_stream: turbo_stream.remove(@message) }
format.html { redirect_to messages_path, notice: "Message deleted" }
end
end
endSpetsialni Shablony Turbo Stream
Dlia skladnishykh vidpovidei shablon .turbo_stream.erb proponuie bilshu hnuchkist.
<!-- app/views/messages/create.turbo_stream.erb -->
<!-- Prepend the new message to the list -->
<%= turbo_stream.prepend "messages" do %>
<%= render @message %>
<% end %>
<!-- Update the counter -->
<%= turbo_stream.update "message_count" do %>
<span><%= Message.count %> messages</span>
<% end %>
<!-- Reset the form -->
<%= turbo_stream.replace "new_message" do %>
<%= render "form", message: Message.new %>
<% end %>
<!-- Display a flash notification -->
<%= turbo_stream.prepend "flash_messages" do %>
<div class="flash flash-success" data-controller="auto-dismiss">
Message sent successfully!
</div>
<% end %>Vidpovidi Turbo Stream povynni maty content-type text/vnd.turbo-stream.html. Rails opratsoovuie tse avtomatychno z respond_to ta formatom turbo_stream.
Broadcasting v Realnomu Chasi z Action Cable
Turbo Streams diisnio blyskuche pratsiuie z WebSocket broadcasting. Korystuvachi otrymaiut onovlennia mytt'ievo bez pollinhu.
# app/models/message.rb
class Message < ApplicationRecord
belongs_to :room
belongs_to :author, class_name: "User"
# Automatic broadcast after creation
after_create_commit do
broadcast_append_to(
room,
target: "messages",
partial: "messages/message",
locals: { message: self }
)
end
# Broadcast after update
after_update_commit do
broadcast_replace_to(
room,
target: dom_id(self),
partial: "messages/message",
locals: { message: self }
)
end
# Broadcast after deletion
after_destroy_commit do
broadcast_remove_to(room, target: dom_id(self))
end
endPodannia pidpysuietsia na vidpovidnyi strim za dopomohoiu helpera turbo_stream_from.
<!-- app/views/rooms/show.html.erb -->
<h1><%= @room.name %></h1>
<!-- Subscribe to the WebSocket stream -->
<%= turbo_stream_from @room %>
<!-- Container for messages -->
<div id="messages">
<%= render @room.messages.order(created_at: :asc) %>
</div>
<!-- New message form -->
<%= form_with model: [@room, Message.new], id: "new_message" do |f| %>
<%= f.text_area :content, placeholder: "Your message..." %>
<%= f.submit "Send" %>
<% end %>Asynchronnyi Broadcasting dlia Vazhkykh Operatsii
Shchob ne blokuvaty zapyty, broadcasting mozhe vykonuvatysia u fonovomu rezhymi.
# app/models/report.rb
class Report < ApplicationRecord
belongs_to :user
after_create_commit :generate_async
private
def generate_async
GenerateReportJob.perform_later(self)
end
end# app/jobs/generate_report_job.rb
class GenerateReportJob < ApplicationJob
queue_as :default
def perform(report)
# Simulating a long operation
report.update!(status: "processing")
# Notify user of start
report.broadcast_replace_to(
report.user, :reports,
target: dom_id(report),
partial: "reports/report"
)
# Generate the report
result = ReportGenerator.new(report).generate
report.update!(content: result, status: "completed")
# Notify user of completion
report.broadcast_replace_to(
report.user, :reports,
target: dom_id(report),
partial: "reports/report"
)
end
endProsunuti Paterny z Turbo
Redahuvannia Form v Riadku
Poshyrenyi patern poliahaie u zamini statychnoho vmistu formoiu redahuvannia v riadku.
<!-- app/views/tasks/_task.html.erb -->
<turbo-frame id="<%= dom_id(task) %>">
<div class="task">
<span class="task-content"><%= task.content %></span>
<div class="task-actions">
<%= link_to "Edit", edit_task_path(task) %>
<%= button_to "Delete", task_path(task), method: :delete,
data: { turbo_confirm: "Delete this task?" } %>
</div>
</div>
</turbo-frame><!-- app/views/tasks/edit.html.erb -->
<turbo-frame id="<%= dom_id(@task) %>">
<%= form_with model: @task do |f| %>
<%= f.text_field :content, autofocus: true %>
<div class="form-actions">
<%= f.submit "Save" %>
<%= link_to "Cancel", task_path(@task) %>
</div>
<% end %>
</turbo-frame>Navigatsiia Poza Batkivskym Frejmom
Za zamovchuvannia, posylannia v frejmi zalyshaiutsia v mezhakh tsoho frejmu. Atrybut data-turbo-frame dozvoliaie tsilyty na inshyi frejm abo na vsiu storinku.
<!-- app/views/search/_results.html.erb -->
<turbo-frame id="search_results">
<ul>
<% @results.each do |result| %>
<li>
<!-- This link navigates the entire page, not the frame -->
<%= link_to result.title, result_path(result),
data: { turbo_frame: "_top" } %>
</li>
<% end %>
</ul>
</turbo-frame><!-- app/views/layouts/_sidebar.html.erb -->
<aside>
<!-- This frame loads content and updates main -->
<turbo-frame id="sidebar_nav" target="main_content">
<%= render "navigation" %>
</turbo-frame>
</aside>
<main>
<turbo-frame id="main_content">
<%= yield %>
</turbo-frame>
</main>U seredovyshchi rozrobky vidkryite konsol JavaScript ta vvedite Turbo.setProgressBarDelay(0), shchob odrazhu pobachyty smuzhu prohresu. Turbo.session.drive = false tymchasovo vymykaie Turbo Drive.
Obrobka Pomylok ta Stany Zavantazhennia
Dobryi UX vymahaie obrobky staniv zavantazhennia ta merezhnykh pomylok.
<!-- app/views/shared/_form_with_loading.html.erb -->
<%= form_with model: @model,
data: {
controller: "form-loading",
action: "turbo:submit-start->form-loading#disable turbo:submit-end->form-loading#enable"
} do |f| %>
<%= yield f %>
<button type="submit" data-form-loading-target="submit">
<span data-form-loading-target="text">Save</span>
<span data-form-loading-target="spinner" class="hidden">
<svg class="animate-spin h-5 w-5"><!-- spinner SVG --></svg>
</span>
</button>
<% end %>import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["submit", "text", "spinner"]
disable() {
this.submitTarget.disabled = true
this.textTarget.classList.add("hidden")
this.spinnerTarget.classList.remove("hidden")
}
enable() {
this.submitTarget.disabled = false
this.textTarget.classList.remove("hidden")
this.spinnerTarget.classList.add("hidden")
}
}Obrobka Pomylok Servera
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound do |exception|
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
"main_content",
partial: "shared/not_found"
)
end
format.html { render "errors/not_found", status: :not_found }
end
end
endOptymizatsiia Produktyvnosti
Kilka tekhnologij zabezpechuiut produktyvni dodatky Turbo.
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
# Avoid N+1 queries with eager loading
def index
@messages = Message.includes(:author, :room)
.order(created_at: :desc)
.page(params[:page])
end
# Fragment caching for static elements
def show
@message = Message.find(params[:id])
fresh_when @message
end
end<!-- app/views/messages/_message.html.erb -->
<!-- Partial-level caching -->
<% cache message do %>
<turbo-frame id="<%= dom_id(message) %>">
<div class="message">
<%= message.content %>
<small>By <%= message.author.name %></small>
</div>
</turbo-frame>
<% end %>Vysnovok
Hotwire ta Turbo transformuiut rozrobku na Rails, usuvaiuchy skladnist tradytsiinykh JavaScript-freimvorkiv. Turbo Drive pryskoruie navigatsiiu, Turbo Frames dozvoliaiut tsilespriamovani onovlennia, a Turbo Streams proponuie potuzhni mozhlyvosti v realnomu chasi.
Chek-lyst dlia Pochatku Roboty z Hotwire
- Vykorystovuvaty Rails 7+ dlia vbudovanoii integratsiiu z Hotwire
- Zrozumity try komponenty: Drive, Frames ta Streams
- Identyfikuvaty zony storinky, shcho skorystaiiut vid Turbo Frames
- Vykorystovuvaty
respond_tozformat.turbo_streamv kontrolerakh - Vprovadyty broadcasting dlia funktsii realnoho chasu
- Poiednaty zi Stimulus dlia prostykh JavaScript-vzaiemodii
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Tsei pidkhid "HTML cherez drit" dozvoliaie stvoriuvaty suchasni, reaktyvni dodatky, zberigaiuchy prostotu ta produktyvnist, shcho roblyat Ruby on Rails takym potuzhnym. Rezultat: menshe kodu dlia pidtrymky, chudova produktyvnist ta optymalnyi dosvid rozrobnyka.
Теги
Поділитися