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:
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.
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:
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.
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.
I hope you enjoyed this article and if you want to keep updated with latest stuff we are building or blogging about, follow us on twitter&bsp;
@codemancershq