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