Rails Forms Deep — Advanced Form Building in Rails
In this tutorial, you will learn about Rails Forms Deep. We cover key concepts, practical examples, and best practices to help you master this topic.
Rails forms use form_with for model-backed and standalone forms, supporting nested attributes, collection helpers, custom form builders, file uploads with Active Storage, and remote submissions.
What You'll Learn
By the end of this tutorial, you'll build model-backed forms, handle nested attributes for associations, use collection helpers, create custom form builders, and implement file uploads.
Why Forms Matter
Forms are the primary way users create and edit data. Rails form helpers handle CSRF protection, error display, and association forms automatically.
Real-World Use
A product form includes nested fields for variants, uses collection select for categories, Active Storage for images, and a custom form Builder for consistent Bootstrap styling.
Forms Path
flowchart LR
A[Rails Views] --> B[Forms Deep]
B --> C[form_with]
B --> D[Nested Forms]
B --> E[Collection]
B --> F[Custom Builders]
B --> G{You Are Here}
style G fill:#f90,color:#fff
Model-Backed Forms
Use form_with with a model.
<%= form_with model: @product, local: true, class: "product-form" do |form| %>
<% if @product.errors.any? %>
<div class="error-messages">
<h2><%= pluralize(@product.errors.count, "error") %> prevented saving:</h2>
<ul>
<% @product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :name %>
<%= form.text_field :name, placeholder: "Product name", required: true %>
</div>
<div class="field">
<%= form.label :price %>
<%= form.number_field :price, step: 0.01, min: 0 %>
</div>
<div class="field">
<%= form.label :category_id %>
<%= form.collection_select :category_id, Category.order(:name), :id, :name, prompt: "Select category" %>
</div>
<div class="field">
<%= form.label :published %>
<%= form.check_box :published %>
</div>
<div class="actions">
<%= form.submit class: "btn btn-primary" %>
<%= link_to "Cancel", products_path, class: "btn btn-secondary" %>
</div>
<% end %>
Nested Attributes
Build nested forms for associations.
class Product < ApplicationRecord
has_many :variants, dependent: :destroy
accepts_nested_attributes_for :variants, allow_destroy: true, reject_if: :all_blank
end
<%= form_with model: @product do |form| %>
<%= form.fields_for :variants do |variant_form| %>
<div class="nested-fields">
<%= variant_form.label :name %>
<%= variant_form.text_field :name %>
<%= variant_form.label :sku %>
<%= variant_form.text_field :sku %>
<%= variant_form.label :price %>
<%= variant_form.number_field :price, step: 0.01 %>
<% if variant_form.object.persisted? %>
<%= variant_form.label :_destroy, "Remove" %>
<%= variant_form.check_box :_destroy %>
<% end %>
</div>
<% end %>
<%= link_to_add_fields "Add Variant", form, :variants %>
<% end %>
Custom Form Builder
Create reusable form builders.
# app/helpers/bootstrap_form_builder.rb
class BootstrapFormBuilder < ActionView::Helpers::FormBuilder
def text_field(method, options = {})
super(method, options.merge(class: "form-control #{'is-invalid' if @object.errors[method].any?}"))
end
def label(method, text = nil, options = {}, &block)
super(method, text, options.merge(class: "form-label"), &block)
end
def submit(text = nil, options = {})
super(text, options.merge(class: "btn btn-primary"))
end
def error_messages
return "" unless @object.errors.any?
@object.errors.full_messages.map { |msg| content_tag(:div, msg, class: "alert alert-danger") }.join.html_safe
end
end
Collection Helpers
Render collection-based form controls.
<!-- Collection select -->
<%= form.collection_select :category_id, Category.all, :id, :name, { prompt: "Choose..." }, { class: "form-select" } %>
<!-- Collection radio buttons -->
<%= form.collection_radio_buttons :category_id, Category.active, :id, :name do |b| %>
<div class="radio-option">
<%= b.radio_button %>
<%= b.label %>
</div>
<% end %>
<!-- Collection checkboxes for has_many -->
<%= form.collection_check_boxes :tag_ids, Tag.all, :id, :name do |b| %>
<div class="checkbox-option">
<%= b.check_box %>
<%= b.label %>
</div>
<% end %>
Common Mistakes
1. Not Using Model-Backed Forms
form_with model: @product handles CSRF, error display, and field population automatically.
2. Missing allow_destroy for Nested Forms
accepts_nested_attributes_for without allow_destroy prevents removing nested records.
3. Rejecting Nested Attributes Incorrectly
reject_if: :all_blank rejects if all fields are blank. Customize with a Proc.
4. Not Handling File Upload Errors
File validations need both model validations and frontend checks.
5. Forgetting Local: True for Non-Turbo Forms
Turbo handles forms by default. Use local: true to bypass Turbo.
Practice Questions
1. What does accepts_nested_attributes_for do?
Allows creating and updating associated records through the parent form.
2. How do you add a collection select?
Use form.collection_select :category_id, Category.all, :id, :name.
3. What is a custom form builder?
A class inheriting from ActionView::Helpers::FormBuilder that customizes form rendering.
4. How do you allow deleting nested records?
Add allow_destroy: true to accepts_nested_attributes_for and a _destroy checkbox.
5. Challenge: Create a product form with nested variant fields.
<%= form_with model: @product do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :variants do |vf| %>
<%= vf.text_field :name %>
<%= vf.number_field :price %>
<% end %>
<%= f.submit %>
<% end %>
FAQ
Mini Project: Product Form with Nested Variants
Build a complete product form with nested variant management.
<%= form_with model: @product, builder: BootstrapFormBuilder do |f| %>
<%= f.error_messages %>
<%= f.label :name %>
<%= f.text_field :name %>
<h3>Variants</h3>
<%= f.fields_for :variants do |vf| %>
<%= vf.text_field :name %>
<%= vf.text_field :sku %>
<%= vf.number_field :price %>
<%= vf.check_box :_destroy %> Remove
<% end %>
<%= f.submit %>
<% end %>
What's Next
Rails Authentication Devise Deep Rails Cancancan Rails API Mode
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro