Form objects for complex form handling

Sarah Mitchell Feb 2026
2 tabs
class UserRegistrationForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :email, :string
  attribute :name, :string
  attribute :password, :string
  attribute :password_confirmation, :string
  attribute :bio, :string
  attribute :accept_terms, :boolean

  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :name, presence: true, length: { minimum: 2 }
  validates :password, presence: true, length: { minimum: 8 }
  validates :password_confirmation, presence: true
  validates :accept_terms, acceptance: true
  validate :passwords_match

  def save
    return false unless valid?

    ActiveRecord::Base.transaction do
      user = create_user
      create_profile(user)
      send_welcome_email(user)
      user
    end
  rescue ActiveRecord::RecordInvalid => e
    errors.add(:base, e.message)
    false
  end

  private

  def passwords_match
    if password != password_confirmation
      errors.add(:password_confirmation, "doesn't match password")
    end
  end

  def create_user
    User.create!(
      email: email,
      name: name,
      password: password
    )
  end

  def create_profile(user)
    user.create_profile!(bio: bio)
  end

  def send_welcome_email(user)
    UserMailer.welcome(user).deliver_later
  end
end

# Controller usage:
# def create
#   @form = UserRegistrationForm.new(registration_params)
#
#   if @form.save
#     redirect_to root_path, notice: "Welcome!"
#   else
#     render :new
#   end
# end
2 files · ruby Explain with highlit

Form objects encapsulate form logic separate from models. I use form objects for multi-model forms, complex validations, or forms not directly mapping to models. Form objects include ActiveModel modules for validations and callbacks. They handle parameter whitelisting, validation, and persistence. Form objects keep models focused on business logic, controllers thin. They're especially useful for wizard-style forms spanning multiple steps. Testing form objects is straightforward—no database setup needed for validation tests. Form objects improve code organization in complex applications. Following Single Responsibility Principle, they make forms maintainable and testable.