Of late, at Codemancers, we are using form objects to decouple forms in views. This also helps in cleaning up how the data filled by end user is consumed and persisted in the backend. So, far the results have been good.

What are form objects

This blog post assumes that you are already familiar with form objects. Railscasts has a nice screencast about form objects. Do check it out if you haven’t already.

Use case

Let’s say that there is an organization and it has several employees. We’re tasked to build a Rails app that provides an interface where an admin can select one or more employees and send them emails. A typical interface implementation might look like this:

After selecting employees, filling-in the subject and body, and upon clicking “Send”, the backend should send emails to the selected employees. This is done by passing the array of the ids of employees, the subject and body to the backend. The POST parameters for that request look like this:

{ "utf8" => "✓" , "email_form" => { "employee_ids" =>[ "" ] , "subject" => "" , "body" => "" }, "commit" => "Send emails to employees" }

Mass mailer form

We will create a EmployeesMassMailerForm form to encapsulate the validations and performing the actual action of sending email. This form should accept the params sent by the form, perform validations like checking whether all the employee ids belong to organization etc., and then send the emails.

class Organization < ActiveRecord :: Base def get_employees ( ids ) employees . where ( id : ids ) end end class EmployeeMassMailerForm include ActiveModel :: Model attr_accessor :organization , :employee_ids , :subject , :body validates :organization , :employee_ids , :subject , :body , presence : true validate :employee_ids_should_belong_to_organization def perform return false unless valid? @employees = organization . get_employees ( xemployee_ids ) @employees . each { | e | schedule_email_for ( e ) } true end private def employee_ids_should_belong_to_organization if organization . get_employees ( employee_ids ) . length != employee_ids . length errors . add ( :employee_ids , :invalid ) end end def schedule_email_for ( e ) Mailer . send_email ( e , subject , body ) end end

With Rails 4, ActiveModel ships with Model module which helps in assigning attributes, just like how you can do with ActiveRecord class, along with helpers for validations. It is no longer necessary to use other libraries for form objects. Just include ActiveModel in a PORO class and you are good to go.

Testing using rspec and shoulda

All the form objects can be broken down into 2 main sections:

Validations Performing actions

Testing validations

Adding validations on forms and models is pretty straight forward. Except for database-related validations like uniqueness , all the ActiveRecord validations can be used on form objects. These validations also make it easy to display validation errors in the view.

At Codemancers, we mostly use rspec and shoulda for testing. Validations on forms can be tested like this:

describe EmployeeMassMailerForm do describe 'Validations' do it { should validate_presence_of ( :organization ) } it { should validate_presence_of ( :employee_ids ) } it { should validate_presence_of ( :subject ) } it { should validate_presence_of ( :body ) } context 'when employee ids belong to organization' do it 'validates form successfully' do employee_ids = [ 1 , 2 ] organization = mock_model ( Organization , get_employees : employees_ids ) form = described_class . new ( organization : organization , subject : 'Test' , employee_ids : employee_ids , body : 'Test' ) expect ( form ) . to be_valid end end context 'when one or more employee ids donot belong organization' do it 'fails to validate the form' do organization = mock_model ( Organization , get_employees : [] ) form = described_class . new ( organization : organization , subject : 'Test' , employee_ids : [ 1 , 2 , 3 ] , body : 'Test' ) expect ( form ) . to be_invalid end end end end

You can notice here that while validating employee ids, we use stubs and mock models so that tests never hit database. Testing a form that has validations is a bit hard, because one has to heavily stub and mock models until form becomes valid. But testing an invalid form is easy and sometimes easy to maintain. Notice that we do not care what get_employees returns and that we hard coded it with an empty array whose length is 0. Always try to put as many validations as possible on form object, so that very less exceptions are raised while performing actions.

Testing actions performed by form

Once all the validations pass, the form object will go ahead and perform the action it is supposed to do. It can be anything from sending emails to persisting objects to database. Lets see how we can test the action perform from above form.

describe EmployeeMassMailerForm do describe '#perform' do let ( :organization ) do employees = [ stub ( email : 'a@b.com' ), stub ( email : 'b@c.com' ) ] mock_model ( Organization , get_employees : employees ) end let ( :form ) do described_class . new ( organization : organization , subject : 'Test' , employee_ids : [ 1 , 2 ] , body : 'Test' ) end before ( :each ) do described_class . any_instance . should_receive ( :valid? ) . and_return ( true ) InvitesMailer . deliveries . clear end it 'sends emails to all employees' do form . perform expect ( InvitesMailer . deliveries . length ) . to eq 2 end it 'returns true' do expect ( form . perform ) . to be_true end end end

The trick here is to hard-code valid? to be true in before block. Since we have already tested validations, we can hard code the return value of valid? to be true . This saves a bunch of db calls and mocks.

If you have any questions or feedback, feel free to drop us a mail at team@codemancers.com