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_forand the newform_with
In a nutshell it 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
endNow here’s how we want our controller to look like:
class ContactsController < ApplicationController
def new
@contact = Contact.new
end
def create
@contact = Contact.new(params[:contact])
if @contact.valid?
# contact_email and to_h methods
# left as an exercise to the reader
Mailer.contact_email(@contact.to_h).deliver
redirect_to root_url , notice: 'Email sent!'
else
render :new
end
end
endvalid? 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
endAn ActiveRecord model has an errors object which is populated when a validation fails — usually after calling save. An ActiveModel object behaves the same way.
Calling valid? on @contact will run its validations and populate its errors object. Afterwards we can use any of these methods in a view:
@contact.errors.messages
#=> { email: ["can't be blank"], name: ["can't be blank"] }@contact.errors.full_messages
#=> ["Email can't be blank", "Name can't be blank"]@contact.errors[:email]
#=> ["can't be blank"]@contact.errors.full_messages_for(:email)
#=> ["Email can't be blank"]And that’s it!
Points worthy of note:
valid?has an alias calledvalidate. It also has an opposite method calledinvalid?. We can use these methods to validate anActiveModelobject and to populate its errors object.- Some people like to add the suffix
Formto their form objects. SoContactForminstead ofContact. This prevents naming collisions withActiveRecordmodels — for example we could have both aUserand aUserForm. Here I opted for the shorter term. In Rails, controllers are suffixed withControllerbut models aren’t suffixed. I view form objects as models not connected to any databases. - Rails automatically loads all files placed in the
appfolder. I always place form objects inapp/forms. I would save thisContactclass inapp/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!
What other uses have you found for form objects? And for ActiveModel? We’d love to hear from you! Comment below with your experiences!
At Runtime Revolution we take our craft seriously and always go the extra mile to deliver a reliable, maintainable and testable product. Do you have a project to move forward or a product you’d like to launch? We would love to help you!
Runtime RevolutionWe are Rails, mobile and product development experts. We can build your product or work with you on your project.www.runtime-revolution.com