Membedah form Rails: form_with sebagai helper form terintegrasi model, nested forms untuk relasi has_many, file upload, serta strong parameters dengan require/permit dan params.expect sebagai perlindungan mass assignment

Setelah di episode 7 kalian memahami view dan template, episode 8 membahas titik pertemuan antara view dan controller: forms. Form adalah pintu masuk data pengguna ke aplikasi — dan karena itu juga pintu masuk utama bagi serangan jika tidak dikelola dengan benar.
Mengapa episode ini penting? Karena form melibatkan tiga lapisan sekaligus: ERB (menampilkan form), controller (membaca dan whitelist data), dan model (validasi & persist). Episode 8 memastikan ketiganya tersambung dengan benar, sekaligus mengenalkan strong parameters — mekanisme yang memblokir mass assignment berbahaya sejak 2012 dan tetap menjadi fondasi keamanan Rails hingga kini.
Helper form utama Rails adalah form_with. Ketika diberi objek model, ia otomatis menyimpulkan action, method, dan field:
<%= form_with model: @post do |form| %>
<% if @post.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(@post.errors.count, "error") %>:</h2>
<ul>
<% @post.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<%= form.submit %>
<% end %>Kecerdasan form_with model: @post:
@post baru → POST /posts (action create).@post sudah tersimpan → PATCH /posts/:id (action update).post[title].Note
Rails 8 memakai form_with sebagai default. form_for/form_tag (Rails 5) sudah dihapus — jika menemui tutorial lama yang memakainya, konversikan ke form_with.
Ketika form disubmit, browser mengirim POST /posts dengan payload post[title], post[body]. Controller membaca dan menyaring data ini. Inilah strong parameters:
def post_params
params.require(:post).permit(:title, :body)
endrequire(:post) — memaksa payload punya key post, jika tidak → ActionController::ParameterMissing.permit(:title, :body) — hanya kolom yang terdaftar yang boleh masuk; sisanya diabaikan.Rails 8 menawarkan params.expect — versi yang lebih eksplisit dan membutuhkan kehadiran key dengan cara yang lebih tegas:
def post_params
params.expect(post: [:title, :body])
endexpect menangani nested object dengan lebih rapi dan melempar error yang lebih jelas saat struktur payload tidak sesuai. Untuk form sederhana, keduanya setara; untuk form kompleks, expect lebih ringkas.
Tanpa strong params, kode berbahaya ini mungkin terjadi:
@post = Post.new(params[:post]) # DANGER: user bisa set admin/role!Jika tabel posts punya kolom user_id atau published, penyerang bisa mengirim POST /posts dengan post[user_id]=999 atau post[published]=true dan menguasai data. Strong params memblokir kolom yang tidak terdaftar — hanya kolom yang kalian setujui yang sampai ke model.
Danger
Jangan pernah memanggil Post.create(params[:post]) atau assign_attributes tanpa strong params. Walaupun kolom sensitif tidak tampil di form, penyerang tetap bisa mengirim payload manual lewat curl — strong params adalah satu-satunya garis pertahanan di sisi ini.
Untuk form yang menyimpan relasi sekaligus (misal post + comments), Rails menyediakan nested attributes. Pertama, model menyetujui:
class Post < ApplicationRecord
has_many :comments
accepts_nested_attributes_for :comments, reject_if: :all_blank
endLalu form menampilkannya:
<%= form.fields_for :comments do |comment_form| %>
<div>
<%= comment_form.label :body, "Komentar" %>
<%= comment_form.text_area :body %>
</div>
<% end %>Dan controller whitelist nested key:
def post_params
params.expect(post: [:title, :body, comments_attributes: [:id, :body, :_destroy]])
end:id diperlukan agar update menemukan record yang sesuai; :_destroy mengizinkan penghapusan. reject_if: :all_blank mencegah komentar kosong tersimpan.
File upload di form Rails dikombinasikan dengan Active Storage (dibahas penuh di episode 13). Polanya:
<%= form_with model: @post, multipart: true do |form| %>
<%= form.label :cover_image %>
<%= form.file_field :cover_image %>
<% end %>multipart: true wajib agar browser mengirim file. Di controller, file masuk sebagai objek ActionDispatch::Http::UploadedFile pada params yang di-permit.
Alur lengkap form + validasi:
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: "Post berhasil dibuat"
else
render :new, status: :unprocessable_entity
end
endSaat validasi gagal, render :new menampilkan kembali form berisi data lama (karena @post diisi), plus error dari @post.errors. Status :unprocessable_entity (422) memberi tahu browser bahwa isi tidak valid — alih-alih status sukses yang membingungkan.
post_params saat menambah field.params[:post] tanpa strong params — celah mass assignment klasik.:id di permit — update relasi jadi gagal atau membuat duplikat.multipart: true — file tidak terkirim, params kosong.presence di form tapi field tidak di-permit — pengguna melihat error aneh; jaga konsistensi whitelist dan form.Episode 8 membekali kalian form Rails: form_with model: yang menyimpulkan action dan field, strong parameters (require/permit dan params.expect) sebagai garis pertahanan mass assignment, nested forms untuk relasi has_many, file upload, serta pola render ulang form saat validasi gagal.
Inti yang harus dibawa pulang:
form_with model: @post otomatis memilih POST/PATCH sesuai status save.params.expect(post: [...]) — hanya kolom yang di-whitelist yang masuk model.Post.new(params[:post]).accepts_nested_attributes_for + permit comments_attributes.render :new, status: :unprocessable_entity.Di episode 9 selanjutnya kita akan membedah authentication & authorization — has_secure_password, session & cookies, Rails Authentication generator, Pundit untuk policies dan role-based access. Sampai jumpa di episode 9!