Form Objects
Form Objects is a design pattern for handling complex form submissions that involve multiple models or custom validation logic, keeping that complexity out of controllers and ActiveRecord models.
Table of Contents
What is a Form Object in Ruby on Rails?
A Form Object is a plain Ruby object that represents a form and handles its submission. Instead of binding a form directly to an ActiveRecord model, a Form Object acts as an intermediary that accepts input, validates it, and coordinates the creation or update of one or more models.
This pattern is useful when a single form needs to interact with multiple models simultaneously, or when the validation and persistence logic does not map cleanly to a single ActiveRecord model.
Why Are Form Objects Useful?
As forms grow in complexity, controllers and models can accumulate logic that does not belong to either. Form Objects help by:
- Keeping controllers thin by moving form handling logic out
- Avoiding bloated models with validations that only apply to specific forms
- Handling forms that interact with multiple models in one place
- Providing a single object to validate and test form submissions
- Separating persistence logic from presentation and input handling
- Making complex form workflows easier to maintain and reuse
They are especially useful for registration forms, checkout flows, or any form that creates or updates more than one record at a time.
How Do Form Objects Work?
A Form Object is a plain Ruby class that includes validation and persistence logic for a specific form.
Key components:
- Form Class – A plain Ruby object that represents the form and its fields
- Validations – Defined on the form object rather than directly on models
- Persistence – The form object coordinates saving one or more models on valid submission
- Controller Integration – The controller passes params to the form object and checks its validity
- Separation of Concerns – Keeps model and controller focused on their primary responsibilities
Examples
Scenario 1: User Registration Form
A registration form that creates both a User and an Account record on submission.
Form Object (app/forms/registration_form.rb)
class RegistrationForm include ActiveModel::Model attr_accessor :first_name, :last_name, :email, :password, :account_name validates :first_name, :last_name, :email, :password, :account_name, presence: true validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } def save return false unless valid? ActiveRecord::Base.transaction do account = Account.create!(name: account_name) User.create!( first_name: first_name, last_name: last_name, email: email, password: password, account: account ) end true rescue ActiveRecord::RecordInvalid false end end
Including ActiveModel::Model gives the form object access to Rails validations, error messages, and form helper compatibility.
Controller
class RegistrationsController < ApplicationController def new @form = RegistrationForm.new end def create @form = RegistrationForm.new(registration_params) if @form.save redirect_to root_path, notice: "Registration successful" else render :new end end private def registration_params params.require(:registration_form).permit( :first_name, :last_name, :email, :password, :account_name ) end end
View
<%= form_with model: @form, url: registrations_path do |f| %> <%= f.text_field :first_name %> <%= f.text_field :last_name %> <%= f.email_field :email %> <%= f.password_field :password %> <%= f.text_field :account_name %> <%= f.submit "Register" %> <% end %>
The view remains clean because it interacts with the form object just like a regular ActiveRecord model.
Scenario 2: Handling Validation Errors
Since the form object includes ActiveModel::Model, validation errors work the same way as with ActiveRecord models.
form = RegistrationForm.new(email: "invalid-email", account_name: "") form.valid? # => false form.errors.full_messages # => ["First name can't be blank", "Email is invalid", "Account name can't be blank"]
This makes error display in views straightforward using the standard Rails error helpers.
Where to Use Form Objects?
- Registration or onboarding flows that create multiple records
- Checkout or order forms involving several models
- Forms with validations that only apply in a specific context
- Multi-step form workflows
- Any form where the input does not map cleanly to a single model
In Summary
A Form Object is a plain Ruby object that handles the validation and persistence logic for complex form submissions, keeping controllers thin and models focused on business logic. It is particularly valuable when a single form needs to interact with multiple models or apply context-specific validations.