SRP on Rails: Form Objects

Rails tools for creating forms and REST controllers are great, easy, and get the job done in most cases, and with very little effort from our side.

Editing the logged in users name, creating a new address, etc, are good examples of actions that can be done automatically. The view shows the fields, pre-populates the data and then with the controller you can mass-assign the fields, run the validations and save in just a couple of method calls.

All is well then. Until you need to move from the righteous path. There are some cases when just doing this is not enough, at least if you’re concerned with the health of your project on the long run.

When you need to read some fields that do not belong in the database

Let’s say we have an Event . It will have a start and end time and date. But, to simplify this for the user, we’re just going to require the start date, and the length of the event (in hours), and do the maths ourselves.

We could add a #length_of_the_event temporary field in the Event , and a #before_create callback, like this:

class Event < ActiveRecord::Base attr_accessor :length_of_the_event validates :length_of_the_event, numericality: {greater_than: 0}, on: :create before_create :calculate_end_dat def calculate_end_date self.end_date = self.start_date + length_of_the_event.hours end end

Will this work? Of course!

…but the problem is, it is adding logic to the Event class, logic that only makes sense while we are creating the Event using the specific use case from before. The rest of the time we’ll be using the event class for our application, and it will have those methods there, which are of no use to us. Even worst, if you tried to create an Event setting the end date, that would not work. You’d have to add logic to disable the effects of the callback in that case.

Form Object to the rescue

In this case, we could put that logic in a Form Object.

class EventForm < Syrup::FormObject wraps :event attribute :length_of_the_event, Integer validates :length_of_the_event, numericality: {greater_than: 0} before_validation :before_validation def before_validation self.end_date = event.start_date + length_of_the_event.to_i.hours end end

Here we moved all the logic for this fields into this FormObject . The Event now has no knowledge of the field, does not need to validate it. Could this logic be added to the controller? Sure. But in that case, you’d have a controller with a lot of logic about how to create the object, on top of handling the parameters and rendering the views.

The resulting form object does not add a lot of code to the overall application, and allow us to separate this logic from the Event class.

External sources

https://github.com/alexsiri7/syrup_form_object/ My implementation of the Form Objects

https://github.com/apotonick/reform Another implementation of Form Objects

https://github.com/brycesenz/freeform Yet another implementation

http://railscasts.com/episodes/416-form-objects

http://pivotallabs.com/form-backing-objects-for-fun-and-profit/

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/