Ruby on Rails 7: Reaktif Uygulamalar icin Hotwire ve Turbo
Rails 7'de Hotwire ve Turbo icin kapsamli rehber. Turbo Drive, Frames ve Streams ile JavaScript yazmadan reaktif uygulamalar gelistirme.

Rails 7, Hotwire'i varsayilan olarak entegre ederek web gelistirmeyi devrimlestirildi. Bu stack, tek bir satir ozel JavaScript yazmadan son derece reaktif uygulamalar olusturmayi mumkun kiliyor. Turbo Drive, Turbo Frames ve Turbo Streams, geleneksel SPA yaklasimlarini "kablo uzerinden HTML" felsefesiyle degistiriyor.
Hotwire, frontend karmasikligini onemli olcude azaltir. Dinamik arayuzler icin React veya Vue gerekmez: sunucu kullanima hazir HTML gonderir ve Turbo, DOM guncellemelerini otomatik olarak yonetir.
Hotwire Mimarisini Anlamak
Hotwire, tipik JavaScript framework karmasikligi olmadan akici bir kullanici deneyimi sunmak icin birlikte calisan uc tamamlayici teknolojiden olusur.
Turbo Drive, baglanti tiklamalarini ve form gonderimlerini yakalayarak gezinmeyi hizlandirir. Sayfanin tamami yeniden yuklenmek yerine, yalnizca <body> icerigi degistirilir ve JavaScript ile CSS baglami korunur.
Turbo Frames, sayfalari bagimsiz bolumlere ayirir. Her frame ayri ayri guncellenebilir, bu da sayfanin geri kalanini etkilemeden hedefli etkilesimlere olanak tanir.
Turbo Streams, WebSocket uzerinden veya HTTP isteklerine yanit olarak gercek zamanli guncellemeler saglar. Bildirimsel DOM manipulasyonu icin sekiz CRUD eylemi mevcuttur.
# Gemfile
# Installing Turbo Rails (included by default in Rails 7+)
gem 'turbo-rails'Mevcut projeler icin kurulum yalnizca bir komut gerektirir.
# 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"Turbo Drive Baslangic Yapilandirmasi
Turbo Drive, Rails 7'de varsayilan olarak etkindir. Tum gezinme ek yapilandirma olmadan otomatik olarak "turbo" haline gelir. Davranis, data ozellikleri araciligiyla ozellestirilebilir.
<!-- 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, gerektiginde belirli baglantilar veya formlar icin devre disi birakilabilir.
<!-- 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 ziyaret edilen sayfalari onbellegine alir. Varliklardaki data-turbo-track="reload" ozelligi, CSS/JS dosyalari degistiginde tam bir yeniden yuklemeyi zorlar.
Hedefli Guncellemeler icin Turbo Frames
Turbo Frames, bagimsiz olarak guncellenen sayfa bolgeleri tanimlar. Her frame benzersiz bir tanimlayiciya sahiptir ve yalnizca eslesen bir frame iceren yanitlara yanit verir.
<!-- 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>Sunucu yaniti, guncellemenin calismasi icin ayni tanimlayiciya sahip bir frame icermelidir.
<!-- 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>Turbo Frames ile Lazy Loading
Frameler, src ozelligi kullanilarak iceriklerini asenkron olarak yukleyebilir. Yukleme sirasinda ilk icerik goruntulenir.
<!-- 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>Denetleyici, yalnizca istenen frame'i iceren bir gorunumle yanit verir.
# 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 mülakatlarında başarılı olmaya hazır mısın?
İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.
Gercek Zamanli Guncellemeler icin Turbo Streams
Turbo Streams, DOM manipulasyonu icin sekiz eylem sunar: append, prepend, replace, update, remove, before, after ve morph. Bu eylemler HTTP yaniti veya WebSocket araciligiyla tetiklenebilir.
# 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
endOzel Turbo Stream Sablonlari
Daha karmasik yanitlar icin .turbo_stream.erb sablonu daha fazla esneklik sunar.
<!-- 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 %>Turbo Stream yanitlari text/vnd.turbo-stream.html content-type'ina sahip olmalidir. Rails bunu respond_to ve turbo_stream formati ile otomatik olarak yonetir.
Action Cable ile Gercek Zamanli Broadcasting
Turbo Streams, WebSocket broadcasting ile gercek gucunu gosterir. Kullanicilar polling olmadan aninda guncellemeler alir.
# 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
endGorunum, turbo_stream_from helper'i ile ilgili stream'e abone olur.
<!-- 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 %>Agir Islemler icin Asenkron Broadcasting
Istekleri engellemekten kacinmak icin broadcasting arka planda gerceklestirilebilir.
# 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
endTurbo ile Ileri Duzey Kaliplar
Satir Ici Form Duzenleme
Yaygin bir kalip, statik icerigi satir ici duzenleme formuyla degistirmeyi icerir.
<!-- 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>Ust Frame Disinda Gezinme
Varsayilan olarak, bir frame'deki baglantilar o frame icinde kalir. data-turbo-frame ozelligi, baska bir frame'i veya tum sayfayi hedeflemeyi saglar.
<!-- 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>Gelistirme ortaminda JavaScript konsolunu acip Turbo.setProgressBarDelay(0) yazarak ilerleme cubugunu hemen gorebilirsiniz. Turbo.session.drive = false Turbo Drive'i gecici olarak devre disi birakir.
Hata Yonetimi ve Yukleme Durumlari
Iyi bir UX, yukleme durumlarinin ve ag hatalarinin yonetilmesini gerektirir.
<!-- 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")
}
}Sunucu Hatasi Yonetimi
# 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
endPerformans Optimizasyonu
Birkac teknik, performansli Turbo uygulamalari saglar.
# 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 %>Sonuc
Hotwire ve Turbo, geleneksel JavaScript frameworklerinin karmasikligini ortadan kaldirarak Rails gelistirmeyi donusturuyor. Turbo Drive gezinmeyi hizlandirir, Turbo Frames hedefli guncellemelere olanak tanir ve Turbo Streams guclu gercek zamanli yetenekler sunar.
Hotwire ile Baslama Kontrol Listesi
- Dahili Hotwire entegrasyonu icin Rails 7+ kullanmak
- Uc bileseni anlamak: Drive, Frames ve Streams
- Turbo Frames'ten fayda saglayacak sayfa bolgelerini belirlemek
- Denetleyicilerde
respond_toileformat.turbo_streamkullanmak - Gercek zamanli ozellikler icin broadcasting uygulamak
- Basit JavaScript etkilesimleri icin Stimulus ile birlestirmek
Pratik yapmaya başla!
Mülakat simülatörleri ve teknik testlerle bilgini test et.
Bu "kablo uzerinden HTML" yaklasimi, Ruby on Rails'i bu kadar guclu kilan basitligi ve uretkenliyi koruyarak modern, reaktif uygulamalar olusturmayi mumkun kilar. Sonuc: daha az bakim gerektiren kod, mukemmel performans ve optimal bir gelistirici deneyimi.
Etiketler
Paylaş