Forms & Strong Parameters

Ruby on Rails Fundamentals — Forms & Strong Parameters
Ruby on Rails Fundamentals
Course 1 · Chapter 8 · Forms & Strong Parameters

📋 Forms & Strong Parameters

This final chapter of the course closes a loop running since Chapter 3: the forms that actually submit to 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.

⚠ Without this, you have a mass assignment vulnerability

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

StepExpressRails
Enable protectionInstall and configure csurf (or a modern equivalent) manuallyOn by default — no setup required
Embed the token in a formManually add a hidden input with the token, in every formAutomatic — form_with does it for you
Verify on submissionMiddleware you wired up yourselfAutomatic — 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.

→ Solution

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.

→ Solution

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.

→ Solution

💎 Security as a Default, Not a Feature You Add

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 Authenticationhas_secure_password/bcrypt and session-based login, contrasted with Express's typical JWT-based approach.