Forms & Strong Parameters
Reviewed & published by Brayan K
Forms are how users hand data to your app. Rails gives you form_with to build them, form helpers to render each field, and strong parameters to accept only the fields you intend to — safely.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll build a model-backed form, whitelist input with params.require.permit, understand CSRF protection, and show a flash message.
What You'll Learn in This Lesson
- Building model-backed forms with form_with
- Form helpers: label, text_field, text_area, submit
- Strong parameters with require and permit
- Why ForbiddenAttributesError happens
- CSRF protection and the authenticity token
- Flash messages after a redirect
1️⃣ Building a Form with form_with
In the view, form_with model: @post creates a form bound to a model. Helpers like form.text_field and form.submit render each control, pre-fill values when editing, and the CSRF token is added automatically.
<%# app/views/posts/new.html.erb %>
<%# form_with builds a form bound to the @post model. It points at the
posts controller, adds the CSRF token automatically, and on submit
sends params nested under :post (e.g. params[:post][:title]). %>
<%= form_with model: @post do |form| %>
<% if @post.errors.any? %>
<div class="errors">
<%= @post.errors.full_messages.to_sentence %>
</div>
<% end %>
<div>
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :body %>
<%= form.text_area :body %>
</div>
<%= form.submit "Save Post" %>
<% end %>
<%# Helpers like text_field, text_area, label, and submit generate the
matching HTML and pre-fill values from @post when editing. %>2️⃣ Strong Parameters in the Controller
In the controller, never pass raw params to a model. Use params.require(:post).permit(:title, :body) to whitelist fields, then branch on save: redirect with a flash on success, or re-render the form on failure.
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def new
@post = Post.new # an empty object for the form
end
def create
@post = Post.new(post_params) # use the PERMITTED params only
if @post.save
# flash[:notice] survives one redirect to show a success message
redirect_to @post, notice: "Post created!"
else
# re-render the form so validation errors display
render :new, status: :unprocessable_entity
end
end
private
# Strong parameters: require the :post key, then whitelist fields.
# Passing raw params to Post.new would raise ForbiddenAttributesError.
def post_params
params.require(:post).permit(:title, :body)
end
endYour turn. Finish a CommentsController#create with a proper strong-parameters method and a flash message.
# 🎯 YOUR TURN — finish a CommentsController create action.
# The form submits params[:comment][:body] (and a post_id).
class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.new(comment_params) # 👉 use strong params
if @comment.save
redirect_to @post, notice: "Comment added!" # 👉 flash message
else
redirect_to @post, alert: "Comment was invalid."
end
end
private
# 1) Require the :comment key and permit ONLY :body
def comment_params
params.require(:comment).permit(:body) # 👉 TODO: complete this
end
end
# ✅ A POST with comment[body]="Nice!" should create a comment and
# redirect back to the post with a success flash.Mini-Challenge: A Contact Form
Build a form_with for a @message and a controller that whitelists name, email, and body, then shows a flash on success.
# 🎯 MINI-CHALLENGE: a contact form and controller.
#
# 1. Build a form_with for a @message (fields: name, email, body).
# 2. In MessagesController#create, define message_params with
# params.require(:message).permit(:name, :email, :body).
# 3. On success: redirect_to root_path, notice: "Thanks!"
# On failure: render :new, status: :unprocessable_entity.
#
# Note WHERE CSRF protection comes from: form_with adds the hidden
# authenticity_token for you, and the controller verifies it.
#
# ✅ Goal: a working create action that only accepts whitelisted fields
# and shows a flash message on success.
# your code hereCommon Errors (and the fix)
- ForbiddenAttributesError — You passed raw params to the model; run them through permit first.
- Param not whitelisted — A field silently goes missing because it wasn't in permit; add it.
- Wrong require key — require(:post) must match how the form nests params.
- Lost flash message — flash survives one redirect; for a render in the same request use flash.now.
- Disabling CSRF — Don't switch off protection to "fix" a token error; ensure the form uses form_with.
Pro Tips
- 💡 Keep a _params method: one private method per resource makes whitelisted fields easy to review.
- 💡 Return 422 on failure: re-render with status: :unprocessable_entity so Turbo shows the errors.
- 💡 Permit nested params: use arrays and hashes in permit for has_many or JSON-style fields.
📋 Quick Reference — Forms & Strong Parameters
| Goal | Code |
|---|---|
| Model-backed form | form_with model: @post |
| Text input | form.text_field :title |
| Submit button | form.submit "Save" |
| Whitelist params | params.require(:post).permit(:title) |
| Success message | redirect_to @post, notice: "..." |
| CSRF token | added by form_with automatically |
🎉 Lesson Complete!
- ✅ form_with model: @post builds a model-backed form
- ✅ Form helpers render labels, inputs, and the submit button
- ✅ params.require(:post).permit(...) whitelists safe fields
- ✅ Raw params raise ForbiddenAttributesError by design
- ✅ CSRF protection and the flash are handled with minimal code
Practice quiz
Which helper is the modern way to build a form bound to a model?
- form_tag
- form_for
- form_with
- make_form
Answer: form_with. form_with is the current unified form helper in Rails.
What is the purpose of strong parameters?
- To speed up the database
- To whitelist which params may be mass-assigned
- To validate the model
- To encrypt the form
Answer: To whitelist which params may be mass-assigned. Strong parameters let only permitted attributes through to the model.
Which call correctly permits title and body for a post?
- params.permit(:post)
- params.allow(:title, :body)
- params.require(:post).permit(:title, :body)
- params[:post].permit_all
Answer: params.require(:post).permit(:title, :body). params.require(:post).permit(:title, :body) requires the post key and whitelists fields.
What does Rails' CSRF protection guard against?
- SQL injection
- Cross-site request forgery on state-changing requests
- Cross-site scripting only
- Slow database queries
Answer: Cross-site request forgery on state-changing requests. CSRF protection blocks forged state-changing requests from other sites.
How does form_with normally include the CSRF token?
- You must add it by hand
- It is added automatically as a hidden field
- Only on GET forms
- It is sent as a cookie
Answer: It is added automatically as a hidden field. form_with injects the authenticity_token hidden field automatically.
Inside form_with |form|, which helper renders a text field for title?
- form.input :title
- text_field(:title)
- form.field :title, :text
- form.text_field :title
Answer: form.text_field :title. form.text_field :title renders the input bound to the model attribute.
What is the flash used for?
- Permanent settings
- A short-lived message shown on the next request
- Caching query results
- Storing the CSRF token
Answer: A short-lived message shown on the next request. The flash holds a message available to the immediately following request, ideal after a redirect.
Why call permit on params instead of using them directly?
- For faster JSON parsing
- To avoid ForbiddenAttributesError and unwanted mass assignment
- To translate the params
- It is purely stylistic
Answer: To avoid ForbiddenAttributesError and unwanted mass assignment. Passing raw params to a model raises ForbiddenAttributesError; permit whitelists safe fields.
Which flash key is conventional for a success message after creating a record?
- flash[:notice]
- flash[:error]
- flash[:warning]
- flash[:debug]
Answer: flash[:notice]. flash[:notice] (often via notice:) is the conventional success message.
If a create action fails validation, what is the usual response?
- Redirect to the index
- Re-render the form with the invalid object and status :unprocessable_entity
- Raise a 500 error
- Silently ignore it
Answer: Re-render the form with the invalid object and status :unprocessable_entity. You re-render :new with the unsaved record so its errors display, using 422.
Continue this course
- Previous: Validations & Callbacks
- Next: REST APIs & Authentication — API mode, JSON responses, sessions, has_secure_password and Devise
- Quick reference: Ruby cheat sheet
Frequently asked questions
Why does Rails require strong parameters?
Strong parameters protect against mass-assignment attacks. Without them, a malicious user could submit extra fields — like admin: true — and have them written straight to your model. By calling params.require(:post).permit(:title, :body) you explicitly whitelist which attributes may be assigned, and any others are silently dropped. If you try to pass raw, unpermitted params into Post.new, Rails raises ForbiddenAttributesError. Keeping the permit list in a private method (often post_params) is the standard pattern and makes the allowed fields easy to audit.
What is CSRF protection and how does form_with handle it?
CSRF (cross-site request forgery) is an attack where another site tricks a logged-in user's browser into submitting a state-changing request to your app. Rails defends against it by embedding a per-session authenticity token in every non-GET form and verifying it on the server; requests without a valid token are rejected. The good news is form_with adds that hidden token field automatically, so you rarely think about it. The protection is switched on by default via protect_from_forgery behaviour in ApplicationController. For JavaScript requests, the token is also exposed in a meta tag.
What is the difference between form_with, form_for, and form_tag?
form_with is the modern, unified helper introduced in Rails 5.1 and is what you should use today. It replaces the two older helpers: form_for, which was bound to a model object, and form_tag, which built a plain form not tied to a model. form_with covers both cases — pass model: @post for a model-backed form, or url: some_path for a standalone form. The older helpers still work for backward compatibility, but new code and the official guides use form_with throughout.
How does the flash work after a redirect?
The flash is a special, short-lived hash for passing a message to the very next request. You typically set it during a redirect — redirect_to @post, notice: "Saved!" — and the layout displays flash[:notice] when the redirected page renders. It survives exactly one redirect and is then cleared, which is why it is perfect for one-off confirmations or errors. Common keys are :notice for success and :alert for problems, though you can use any key. If you need a message on a render (not a redirect) within the same request, use flash.now instead.