Ruby on Rails 7: Hotwire lae Turbo samrap Aepplikheishan Riaethaim

Khumue sombun khong Hotwire lae Turbo nai Rails 7. Sang aepplikheishan riaethaim doi mai tong khian JavaScript duai Turbo Drive, Frames lae Streams.

Khumue Hotwire lae Turbo samrap Ruby on Rails 7

Rails 7 pathiwat kan phatthana web doi kanruam Hotwire pen khakamnot. Stack ni thamhai samartriang sang aepplikheishan thi mi khwam riaethaim sung doi mai tong khian khot JavaScript thi kahamnot eng mae sak brrthad nueng. Turbo Drive, Turbo Frames lae Turbo Streams thaenthi withikan SPA daengdoem duai pratchya "HTML phan sai".

Thammai tueng Hotwire?

Hotwire lot khwam sapson khong frontend yang mak. Mai champen tong chai React rue Vue samrap inthoerefet thi mi kan toptoep: soewoe song HTML thi phrom chai lae Turbo chatkan kap kapnhat DOM doi atonmat.

Khaojai Sathapattayakam Hotwire

Hotwire prakop duai sam thekhnoloyi thi soem kan pen thanglueak tham ngan ruam kan phue mop prasopkan phuchai thi rapruen doi mai mi khwam sapson thi thammada khong framework JavaScript.

Turbo Drive reng khwam reo nai kan naewithang doi kan rap klik linkhae lae kan song baep. Thaen thi cha lotphaena thang mot, cha mi phiang nueaha khong <body> thuk thaenthi, doi rak sa boribot khong JavaScript lae CSS wai.

Turbo Frames baeng phaena pen suan thi pen itsara. Tae la frame samat kapnhat idyaek, thamhai samat mi kan toptoep bep jaomung doi mai kra thop suan thi luea khong phaena.

Turbo Streams het hai samat kapnhat raiaethaim phan WebSocket rue pen kan toptoep HTTP request. Paet kan kra tham CRUD mi hai samrap kan chatkan DOM baep prakat.

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

Samrap porachek thi mi yu laew, kan tittang tong kan phiang kham sang diaw.

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"

Kan Kamnot Kha Roemton Turbo Drive

Turbo Drive thuek poet chai pen kha khamnot nai Rails 7. Kan naewithang thang mot cha pen "turbo" doi atonmat doi mai tong mi kan kamnot kha phoem. Phruettikam samat prap taeng dai thang atthribut data.

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 samat pit kan chai bon link rue baep thi chapho mue champen.

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?" } %>
Khaet Turbo Drive

Turbo Drive khaet phaena thi yiam laew. Atthribut data-turbo-track="reload" bon asset bangkhap kan lot mai thang mot tha fai CSS/JS mi kan plianplaeng.

Turbo Frames samrap Kapnhat Baep Jaomung

Turbo Frames kahamnot son khong phaena thi kapnhat pen itsara. Tae la frame mi tuarabubang thi mi ekkalak lae toptoep phiang kap khamtop thi mi frame thi trongkan.

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>

Khamtop chak soewoe tong mi frame thi mi tuarabubang diaw kan phue hai kan kapnhat tham ngan.

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>

Lazy Loading kap Turbo Frames

Frame samat lot nueaha baep asinkronat doi chai atthribut src. Nueaha roemton cha saadaeng nai rawang kan lot.

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>

Khonthrolen toptoep kap view thi mi phiang frame thi rong kho.

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 แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

Turbo Streams samrap Kapnhat Raiaethaim

Turbo Streams sanoe paet kan kra tham samrap kan chatkan DOM: append, prepend, replace, update, remove, before, after lae morph. Kan kra tham lehla ni samat thuek riak phan khamtop HTTP rue WebSocket.

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

Thaemplet Turbo Stream Chapho

Samrap khamtop thi sapson kwanan, thaemplet .turbo_stream.erb sanoe khwam yuedhyun makkwa.

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 %>
Rup Baep Khamtop

Khamtop Turbo Stream tong mi content-type text/vnd.turbo-stream.html. Rails chatkan singni doi atonmat kap respond_to lae rup baep turbo_stream.

Broadcasting Raiaethaim kap Action Cable

Turbo Streams doden dai di thi sut kap kan broadcasting WebSocket. Phuchai dai rap kapnhat thanthi doi mai tong polling.

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

View samak rap stream thi trongkan duai helper turbo_stream_from.

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 %>

Broadcasting Baep Asinkronat samrap Kan Tham Ngan Nak

Phue likliang kan blok request, broadcasting samat tham dai nai baenglang.

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

Ruepbaep Kanchai Ngan Khansungsamrap Turbo

Kan Kaekhai Baep Inline

Ruepbaeb thi phop boi kiao kap kan thaenthi nueaha thi khong mi kan plianplaeng duai baep kaekhai inline.

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>

Naewithang Nok Frame Lak

Doi kahamnot, link nai frame cha yu nai frame nan. Atthribut data-turbo-frame anuyat hai jaomung pai frame uen rue thang phaena.

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>
Kan Daebaek Turbo

Nai sapha waetlaom kan phatthana, poet JavaScript console lae phim Turbo.setProgressBarDelay(0) phue hen thaeang khwam khaonah thanthi. Turbo.session.drive = false pit Turbo Drive chuakhrao.

Kan Chatkan Khophitphlat lae Sathana Kamlang Lot

UX thi di tong kan kan chatkan sathana kamlang lot lae khophitphlat khruekhai.

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")
  }
}

Kan Chatkan Khophitphlat Soewoe

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

Kan Prapprung Prasiththiphap

Lai thekhnik raprong aepplikheishan Turbo thi mi prasiththiphap.

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 %>

Srupphon

Hotwire lae Turbo plianplaeng kan phatthana Rails doi kan loet khwam sapson khong framework JavaScript daengdoem. Turbo Drive reng khwam reo kan naewithang, Turbo Frames anuyat kan kapnhat baep jaomung, lae Turbo Streams sanoe khwam samat raiaethaim thi trongklang.

Raikantruatsop samrap Roem Ton kap Hotwire

  • Chai Rails 7+ samrap kan ruam Hotwire thi mi ma hai
  • Khaojai sam ongprakop: Drive, Frames lae Streams
  • Rabu son khong phaena thi dai rap phon prayot chak Turbo Frames
  • Chai respond_to kap format.turbo_stream nai khonthrolen
  • Nammaj broadcasting samrap ficoer raiaethaim
  • Ruam kap Stimulus samrap kan toptoep JavaScript thi ngai

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

Withikan "HTML phan sai" ni anuyat hai sang aepplikheishan thi than samai lae mi kan toptoep khana thi rak sa khwam ngai lae phon phlit thi thamhai Ruby on Rails mi phalang makkhana. Phonlap: khot thi tong dulae noi long, prasiththiphap thi yiam yot lae prasopkan nak phatthana thi di thi sut.

แท็ก

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

แชร์