Blog icon

Form Objects in Rails

By Leonard Garvey,
Leonard Garvey
Scroll down to read

Rails applications that go beyond the most basic often require complicated forms, or forms not backed by an ActiveRecord model. Form objects help in this regard, and are a fantastic technique of moving code out of your models.

I like to use form objects whenever I have a slightly complicated form, a form which will write to 2 database models or if I'm creating a form that isn't backed by a database model at all. You can think of the form object as a model of the form itself as displayed to the user. This means you've got a spot to put logic which is related to only the form itself.

You may have heard of form objects before but perhaps you're a little confused on how to use them properly in your Rails applications. If you've got a subscription to RailsCasts you can watch the form object screencast which provides an excellent introduction, but here's the short version:

  1. Create a plain Ruby class.
  2. include ActiveModel::Model (in Rails 3 you had to include Naming, Conversion and Validations instead)
  3. Start using your new form class as if it were a regular ActiveRecord model, the biggest difference being that you cannot persist the data stored in this object.

Example form object implementation

Here's an example form object. I like to put these in app/forms although many people would keep them in app/models.

class ProfileForm
  include ActiveModel::Model

  attr_accessor :name, :email, :subscribed_to_marketing_emails, :age

  validates_presence_of :name, :email
  validates_format_of :email, with: /.+@.+/
  validates_numericality_of :age, allow_nil: true, greater_than: 0

  # instead of doing this you could always remember to provide
  # a default value to the initializer instead: ProfileForm.new(subscribed_to_marketing_emails: true)
  # but that would be frustrating too.
  def subscribed_to_marketing_emails
    @subscribed_to_marketing_email ||= true
  end
end

Your controller for the form might look a little like:

class ProfilesController < ApplicationController
  def new
    @profile_form = ProfileForm.new
  end

...

and your view might look like:

<%= simple_form_for @profile_form, url: profile_path do |f| %>
  <%= f.input :name %>
  <%= f.input :email %>
  <%= f.input :age, as: :integer %>
  <%= f.input :subscribed_to_marketing_emails, as: :boolean %>
<% end %>

The problems

But there are some problems with our code.

  • Having to specify the url in the form helper is frustrating. We can fix this by specifying the model_name on the class of the object we pass to the helper.
  • Having to specify the html representation of the :subscribed_to_marketing_emails and :age fields is frustrating. With simple_form this doesn't happen if you use a normal ActiveRecord object because simple_form infers the type of the html element from the type of the database column.
  • Setting default values for your attributes is a little bit messy. Note that since ActiveModel provides an initializer for our object which accepts a hash like a regular ActiveRecord object, it's not a good idea to override the initialize method.

Enter simpleformobject

Recently I created a very small gem, called SimpleFormObject, which solves the issues above. To refactor the form object code above we'd do the following:

class ProfileForm
  include SimpleFormObject

  attribute :name
  attribute :email
  attribute :subscribed_to_marketing_emails, :boolean, default: true
  attribute :age, :integer

  validates_presence_of :name, :email
  validates_format_of :email, with: /.+@.+/
  validates_numericality_of :age, allow_nil: true, greater_than: 0
end

In this version of the form object we don't need to provide a default using the attribute reader method so it's a little cleaner.

We wouldn't need to change our controller at all but our view is simplified to look like:

<%= simple_form_for @profile_form do |f| %>
  <%= f.input :name %>
  <%= f.input :email %>
  <%= f.input :age %>
  <%= f.input :subscribed_to_marketing_emails %>
<% end %>

Notice here we don't need to specify the url of the form. Nor do we have to specify which html elements we want to create. SimpleFormObject causes each attribute to pretend to be a database column which simple_form will convert automatically to the correct element.

I think this is a small, but useful improvement for form objects. Note that there other fantastic form gems out there. But often I want to stay as close to the Rails defaults as possible and this provides just enough sugar to let me do what I need to do, while getting out of my way.

Let me know what you think!

To find out how reinteractive can turn your web application vision into reality, get in touch with us through our contact form or call us on +61 2 8019 7252.