Form Objects in Rails

Leonard Garvey
February 17, 2014

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.

```ruby 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: 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: ruby class ProfilesController < ApplicationController def new @profile_form = end … and your view might look like: erb <%= 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 simple_form_object 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: ```ruby 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: erb <%= 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!