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.

Ruby on Rails 7 icin Hotwire ve Turbo Rehberi

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.

Neden Hotwire?

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.

ruby
# Gemfile
# Installing Turbo Rails (included by default in Rails 7+)
gem 'turbo-rails'

Mevcut projeler icin kurulum yalnizca bir komut gerektirir.

bash
# 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.

erb
<!-- 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.

erb
<!-- 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 Onbellegi

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.

erb
<!-- 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.

erb
<!-- 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.

erb
<!-- 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.

ruby
# 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
erb
<!-- 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.

ruby
# 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
end

Ozel Turbo Stream Sablonlari

Daha karmasik yanitlar icin .turbo_stream.erb sablonu daha fazla esneklik sunar.

erb
<!-- 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 %>
Yanit Formati

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.

ruby
# 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
end

Gorunum, turbo_stream_from helper'i ile ilgili stream'e abone olur.

erb
<!-- 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.

ruby
# 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
ruby
# 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
end

Turbo ile Ileri Duzey Kaliplar

Satir Ici Form Duzenleme

Yaygin bir kalip, statik icerigi satir ici duzenleme formuyla degistirmeyi icerir.

erb
<!-- 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>
erb
<!-- 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.

erb
<!-- 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>
erb
<!-- 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>
Turbo Hata Ayiklama

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.

erb
<!-- 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 %>
app/javascript/controllers/form_loading_controller.jsjavascript
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

ruby
# 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
end

Performans Optimizasyonu

Birkac teknik, performansli Turbo uygulamalari saglar.

ruby
# 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
erb
<!-- 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_to ile format.turbo_stream kullanmak
  • 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

#ruby on rails
#hotwire
#turbo
#turbo frames
#turbo streams

Paylaş