Practice · Reading the Source · Card 5
What's missing from this hand-written form?
A developer reaches for raw HTML instead of form_with. The form posts, but the update doesn't happen. Three pieces are missing.
The form
For an existing Post (id: 42). The intent is "update the title and body."
<form action="/posts/42" method="post">
<label>Title</label>
<input type="text" name="title">
<label>Body</label>
<textarea name="body"></textarea>
<input type="submit" value="Update Post">
</form> The question
Which set of additions would form_with model: @post have inserted into this form?
Take a moment. Pick the best answer. Wrong picks reveal why they're wrong, which is half the point.
✅ Answer breakdown
✗ A. A jQuery dependency, an async fetch wrapper, and a loading spinner.
form_with is server-rendered HTML. No JavaScript dependency. The form posts the regular way.
✓ B. A CSRF token, a hidden _method field, and field names like post[title].
All three are essential. _method=patch tells Rack::MethodOverride to rewrite the POST as a PATCH so the right route matches. authenticity_token is what protect_from_forgery checks. The post[...] namespacing is what makes params[:post][:title] work on the server.
✗ C. A CORS preflight header, a session ID cookie, and a hidden user ID field.
CORS isn't form-related (it's for cross-origin XHR). The session cookie is sent automatically by the browser, not inserted into the form body. The user ID isn't typically in the form; it comes from the session.
✗ D. Server-side validation messages, ARIA attributes, and Turbo data attributes.
Validation messages are rendered after submission, not built into the form HTML. ARIA and Turbo attributes are nice-to-have but aren't what makes the basic request work.
💡 The principle
An HTML form can only do GET or POST. Rails uses three hidden details to bridge the gap to its full REST surface:
_method— read byRack::MethodOverridemiddleware. Without it, an update form ends up matching the create route.authenticity_token— read byprotect_from_forgery. Without it, the request 422s.name="post[title]"— read by Rack's nested-params parser. Without it, yourparams.require(:post)raises.
Hand-written forms break for one of these three reasons more often than any other. form_with exists so you don't have to remember.
📚 Theory
For the full walkthrough, read Reading the Source · Card 7 — How form_with builds an HTML form.