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:
- Create a plain Ruby class.
include ActiveModel::Model
(in Rails 3 you had to includeNaming
,Conversion
andValidations
instead)- 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: 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:
ruby
class ProfilesController < ApplicationController
def new
@profile_form = ProfileForm.new
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!