Membedah Action View: template ERB, partials yang bisa dipakai ulang, helpers untuk merapikan tampilan, layouts sebagai kerangka halaman, dan Hotwire (Turbo Frames & Turbo Streams) untuk UI interaktif tanpa build JS berat

Setelah di episode 5 dan 6 kalian menguasai sisi data (Active Record & migrations), episode 7 berpindah ke lapisan paling dekat dengan pengguna: views dan Action View. Di sinilah data dari controller diubah menjadi HTML yang benar-benar dilihat user.
Mengapa views penting dikuasai dalam? Karena pengguna tidak pernah melihat model atau controller — yang mereka lihat adalah view. Dan di era Rails 8, view bukan sekadar HTML statis: dengan Hotwire (Turbo + Stimulus), kalian bisa membangun UI interaktif tanpa menulis satu baris JavaScript bundling. Episode ini membekali kalian fondasi template yang menjadi dasar semua episode UI berikutnya.
Views Rails ditulis dalam ERB (Embedded Ruby) — HTML dengan potongan Ruby di dalam <% %>:
<h1>Daftar Post</h1>
<% @posts.each do |post| %>
<article>
<h2><%= link_to post.title, post %></h2>
<p><%= truncate(post.body, length: 120) %></p>
<small>Oleh <%= post.user.name %> · <%= time_ago_in_words(post.created_at) %></small>
</article>
<% end %>Dua jenis tag utama:
<% %> — menjalankan Ruby tanpa output (loop, if).<%= %> — menjalankan Ruby dan menampilkan hasilnya.Escape otomatis adalah fitur keamanan terpenting ERB: semua output <%= %> di-escape HTML secara default, sehingga serangan XSS lewat post.body otomatis diblokir (dibahas penuh di episode 18).
Kode view yang berulang dipindah ke partial — file dimulai dengan underscore:
<article>
<h2><%= link_to post.title, post %></h2>
<p><%= truncate(post.body, length: 120) %></p>
<small>Oleh <%= post.user.name %></small>
</article>Lalu dirender dari view lain:
<%= render partial: "post", collection: @posts, as: :post %>Partial menangani loop sendiri: <%= render @posts %> otomatis me-render _post.html.erb untuk tiap elemen. Partial memakai variabel lokal (post) bukan instance variable — lebih aman dan lebih reusable.
Helper adalah method Ruby yang membantu view. Bawaan Rails sudah banyak: link_to, truncate, time_ago_in_words, number_to_currency, pluralize. Helper custom ditulis di app/helpers/:
module PostsHelper
def status_badge(post)
if post.published?
content_tag(:span, "Published", class: "badge badge-success")
else
content_tag(:span, "Draft", class: "badge badge-muted")
end
end
endDipakai di view: <%= status_badge(post) %>. Prinsipnya: logika tampilan di helper, data di model. Jika logika menyangkut aturan bisnis, letakkan di model, bukan helper.
Layout adalah template pembungkus seluruh halaman. Defaultnya app/views/layouts/application.html.erb:
<!DOCTYPE html>
<html>
<head>
<title>Blog</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
</head>
<body>
<%= render "layouts/navbar" %>
<main class="container">
<% flash.each do |type, msg| %>
<%= content_tag :div, msg, class: "flash #{type}" %>
<% end %>
<%= yield %>
</main>
</body>
</html><%= yield %> adalah titik tempat template action disuntikkan. Flash dari episode 4 dirender di sini agar tampil di semua halaman. Layout khusus (misal layouts/admin) bisa dipilih via layout method di controller.
Rails 8 memakai Hotwire — Turbo + Stimulus — sebagai cara default membangun interaktivitas tanpa menulis JavaScript framework. Prinsipnya: server mengirim HTML, Turbo menukar bagian halaman secara asinkron.
Turbo Frame membatasi area halaman yang di-update:
<turbo-frame id="comments">
<% @post.comments.each do |comment| %>
<p><%= comment.body %></p>
<% end %>
</turbo-frame>Ketika request berasal dari dalam frame (misal submit form komentar), Turbo hanya mengganti isi frame itu — bukan seluruh halaman. Hasilnya terasa seperti SPA tanpa JavaScript manual.
Turbo Streams adalah format respons untuk update realtime — dikirim sebagai text/vnd.turbo-stream.html:
def create
@comment = @post.comments.new(comment_params)
if @comment.save
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.append("comments", @comment) }
format.html { redirect_to @post }
end
end
endturbo_stream.append("comments", @comment) menyuruh Turbo menambahkan _comment.html.erb ke akhir elemen dengan id comments — tanpa reload. Di episode 14, Turbo Streams dikombinasikan dengan Action Cable untuk realtime penuh.
Note
Stimulus (bagian Hotwire) dipakai untuk perilaku JavaScript kecil yang scoped — misal toggle tampil/sembunyi atau fetch sederhana. Stimulus bukan pengganti Turbo: Turbo menangani update HTML, Stimulus menangani interaksi spesifik. Kombinasinya membuat banyak UI tanpa React/Vue.
@post bisa nil; gunakan helper atau pastikan controller selalu mengaturnya. Lebih rapi: terima data lewat partial lokal.current_user.posts.count di dalam helper. Ini merusak pemisahan concern dan bisa memicu N+1. Query di model/controller, helper hanya menampilkan.render dengan pilihan partial.data-turbo-track: "reload" — asset stale; sambungan CSS/JS tak ter-update saat reload.Episode 7 membekali kalian Action View: ERB dengan escaping otomatis, partial untuk kode reusable, helpers untuk merapikan logika tampilan, layouts sebagai kerangka halaman, dan Hotwire — Turbo Frames untuk update terbatas serta Turbo Streams untuk update asinkron tanpa reload.
Inti yang harus dibawa pulang:
<% %> tanpa output, <%= %> dengan output; escaping otomatis anti-XSS._post.html.erb) dipakai ulang via render @posts.<%= yield %> adalah titik injeksi.Di episode 8 selanjutnya kita akan membedah forms & strong parameters — form_with, nested forms, dan file upload, plus permit/require dan perlindungan mass assignment. Sampai jumpa di episode 8!