Forms & Strong Parameters
๐ Forms & Strong Parameters
create and update, the security mechanism preventing a submitted form from setting fields it shouldn't, and the CSRF protection every one of Rails' forms has been quietly including the entire time.
๐ form_with
<!-- app/views/posts/_form.html.erb --> <%= form_with model: post do |f| %> <%= f.label :title %> <%= f.text_field :title %> <%= f.label :body %> <%= f.text_area :body %> <%= f.submit %> <% end %>
form_with model: post inspects the post object and automatically decides everything Express would need spelled out by hand: a new, unsaved Post generates a form that POSTs to /posts (the create action); an existing, persisted Post generates a form that submits as PATCH to /posts/:id (the update action) โ the same partial (from Chapter 4) works for both the new and edit views without any conditional logic inside it.
๐ท๏ธ Field Helpers Generate Rails-Shaped Names
<%= f.text_field :title %>
<!-- generates: -->
<input type="text" name="post[title]" id="post_title" value="...">
f.text_field :title generates an input with name="post[title]" โ matching Chapter 3's nested params[:post][:title] structure automatically. In Express + EJS, this nesting has to be hand-built: writing name="title" directly, or manually adopting a naming convention like name="post[title]" yourself with no framework enforcement or generation.
โ ๏ธ Displaying Validation Errors
<% if post.errors.any? %>
<div class="errors">
<ul>
<% post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_with model: post do |f| %>
...
<% end %>
This is Chapter 7's errors.full_messages put to work: when create fails and re-renders :new (Chapter 3), the same @post instance โ invalid, but still holding the user's submitted values and the validation errors โ flows straight into this partial with no extra wiring.
๐ Strong Parameters
# app/controllers/posts_controller.rb class PostsController < ApplicationController def create @post = Post.new(post_params) # ... end private def post_params params.require(:post).permit(:title, :body, :published) end end
params.require(:post) raises an error if the post key is missing entirely; .permit(:title, :body, :published) explicitly whitelists exactly which fields are allowed through โ anything else submitted in the form gets silently dropped, never reaching Post.new.
Imagine Post.new(params[:post]) instead โ passing the entire submitted hash straight into the model with no whitelist. If Post ever gains an admin_only or featured boolean column, a malicious user could add a hidden field, post[featured]=true, to their form submission (using browser dev tools, or a crafted request bypassing the UI entirely) and set a field the form never intended to expose. This is a mass assignment vulnerability โ trusting raw user input to determine which attributes get set, the same "never trust user input" principle underlying every completed security course, just applied specifically to model attribute assignment.
๐ก๏ธ CSRF Protection, Built In
# app/controllers/application_controller.rb -- present by default in every new Rails app class ApplicationController < ActionController::Base protect_from_forgery with: :exception end
Recall the completed CSRF security course: preventing cross-site request forgery requires a unique, unpredictable token embedded in every form and verified on submission. Rails generates this automatically โ protect_from_forgery is present in ApplicationController from the moment rails new runs (Chapter 1), and every form_with call silently embeds the token as a hidden field with zero additional code required.
CSRF Protection: Express vs Rails
| Step | Express | Rails |
|---|---|---|
| Enable protection | Install and configure csurf (or a modern equivalent) manually | On by default โ no setup required |
| Embed the token in a form | Manually add a hidden input with the token, in every form | Automatic โ form_with does it for you |
| Verify on submission | Middleware you wired up yourself | Automatic โ built into ApplicationController |
๐ป Coding Challenges
Challenge 1: A Product Form
Write app/views/products/_form.html.erb using form_with model: product with fields for name (text field), description (text area), and in_stock (checkbox), plus a submit button.
Goal: Practice the standard form_with + field helper pattern for a realistic set of field types.
Challenge 2: Strong Parameters and a Mass Assignment Attack
Write the product_params private method for a ProductsController, permitting only name, description, and price โ deliberately excluding a featured boolean column that exists on the table. Then write two sentences describing exactly what a malicious form submission would need to include to try to set featured, and why product_params as written stops it.
Goal: Understand strong parameters as an active defense, not just boilerplate.
Challenge 3: Displaying Errors in a Form
Add an error-display block (following this chapter's pattern) above a form_with model: product form, then trace through what happens end to end: a user submits an invalid product, create fails, and the form re-renders. Write out, step by step, which chapter's concept handles each part of that flow (validation, the branch in the controller, the errors object, the re-rendered view).
Goal: Connect this chapter's form back to the validations (Chapter 7) and controller logic (Chapter 3) it depends on.
Look back across this course: automatic HTML escaping (Chapter 4), automatic SQL injection protection through the query interface (Chapter 5), and now strong parameters and built-in CSRF protection โ every one of them is the same underlying pattern this course keeps returning to. Rails doesn't just make the common case shorter to write; it consistently makes the common case the secure case, requiring a deliberate, visible opt-out (raw(), string-interpolated SQL, skipping permit) to reach the dangerous version instead of a deliberate opt-in to reach the safe one.
๐ฏ What's Next
Course 1 (Ruby on Rails Fundamentals) is complete. Ruby on Rails Intermediate/Advanced (Course 2) begins with Authentication โ has_secure_password/bcrypt and session-based login, contrasted with Express's typical JWT-based approach.