Form objects are an informal pattern for separating validation logic from persistence logic and reducing the complexity of your models, making them easier to test and reason about. This guide shows you how to use form objects in your Rails application.
The form object
First step, add the formeze gem to your Gemfile (and run bundler to install):
gem 'formeze', '~> 4'
You can then define a form object by inheriting from Formeze::Form
and declaring the form fields. For example, here’s how you might define a form for adding products:
require 'formeze'
class ProductForm < Formeze::Form
field :title, scrub: [:strip]
field :description, multiline: true, maxlength: 2000, scrub: [:strip]
field :size, required: false, values: %w(Small Medium Large)
end
The different field options are detailed in the formeze README.
This class can then be used to automatically parse, scrub, and validate form data submitted to your application, irrespective of where or how the data is persisted.
The view
The view corresponding to the form object above might look something like this:
<%= form_tag action: :create do %>
<input type="text" name="title" value="<%= @form.title %>">
<textarea name="description"><%= @form.description %></textarea>
<select name="size">
<%= options_for_select %w(Small Medium Large), @form.size %>
</select>
<input type="submit" value="Add product">
<% end %>
The field values can be accessed as attributes on the form object. Either you can use regular HTML for the form inputs, the Rails form helpers, or a combination of both.
The controller
The controller corresponding to the form object and view above might look like this:
class ProductsController < ApplicationController
def new
@form = ProductForm.new
end
def create
@form = ProductForm.new.parse(request)
if @form.valid?
# save form data
# @form.to_h returns a hash of field names and values
# fields can also be accessed as attributes like @form.description
else
render :new, status: :unprocessable_entity
end
end
end
If the form is valid you’ll typically pass the data to your model layer. If the form is invalid you’ll typically re-render the form so the user can make changes and re-submit.
Displaying validation errors
Re-rendering the form without any indication of why it won’t save is confusing for users, so in most cases you’ll want to display the validation errors to the user, for example:
<% if @form.errors? %>
<div class="errors">
<ul>
<% @form.errors.each do |error| %>
<li><%= error %></li>
<% end %>
</ul>
</div>
<% end %>
This is similar to how you would display ActiveRecord validation errors in views, except that the validations and errors are defined on the form object, not your model object.
That’s all there is to it. Give it a try in your next application!