A form object is an object designed specifically to be passed to form_for. It is often used to aggregate data to create multiple objects or to receive ephemeral data that is used and then discarded.

Rails 4 introduced a small handy module called ActiveModel::Model. A Ruby class can mix in this module and gain a ton of functionality, including:

  • initialization with a hash of attributes

  • validation of attributes

  • presentation of errors

  • interaction with view helpers like form_for and the new form_with

It basically allows a Ruby object to quack like an ActiveRecord model without being backed by a database table. Exactly what we need to implement a form object.

A contact form is one of the easiest ways to acquire leads. Here’s how we want our view to look like — a simple form with 3 inputs and a button:

<%= form_for @contact do |f| %>
  <%= f.label :name %>
  <%= f.text_field :name %>

  <%= f.input :email %>
  <%= f.email_field :email %>

  <%= f.input :message %>
  <%= f.text_field :message %>

  <%= f.button :submit, 'Send message' %>
<% end %>

How could we make this work with a Ruby object? Simple! Include ActiveModel::Model:

class Contact
  include ActiveModel::Model

  attr_accessor :email, :message, :name

Now here’s how we want our controller to look like:

class ContactsController < ApplicationController
  def new
    @contact = Contact.new

  def create
    @contact = Contact.new(params[:contact])
    if @contact.valid?
      # contact_email and to_h methods left as an exercise to the reader
      redirect_to root_url, notice: 'Email sent!'
      render :new

valid? is there to guarantee that a contact is valid before we send an email. Let’s add some validations like we would with any ActiveRecord model:

class Contact
  # ...

  validates :email, :message, :name, presence: true

An ActiveRecord model has an errors object that is populated when a validation fails — usually after calling save. An ActiveModel object behaves in a similar fashion.

Calling valid? on @contact will run its validations and populate its errors object. Afterwards we can use any of these methods in our code:

#=> { email: ["can't be blank"], name: ["can't be blank"] }

#=> ["Email can't be blank", "Name can't be blank"]

#=> ["can't be blank"]

#=> ["Email can't be blank"]

And that’s it!

Points worthy of note:

  • valid? has an alias called validate. It also has an opposite method called invalid?. We can use these methods to validate an ActiveModel object and to populate its errors object.

  • Some people like to add the suffix Form to their form objects. So ContactForm instead of Contact. This prevents naming collisions with ActiveRecord models — for example we could have both a User and a UserForm. Here I opted for the shorter term. In Rails, controllers are suffixed with Controller but models aren’t suffixed. I view form objects more as models not connected to any databases.

  • Rails automatically loads all files placed in the app folder. I always place form objects in app/forms. I would save this Contact class in app/forms/contact.rb. But this is a convention, not a rule.

Now go on and create your own form objects. Add contextual validations. Simplify your code!