SRP on Rails: Form Objects (part 2)

This is the second in a series of posts about Form objects. The first one can be found here.

Ok, for our Events application, we got the user to create the event with it’s most basic information. Now we want them to be able to add the details.

They need to add information about the price of the ticket, and about the Venue . The price of the tickets will be a part of the Event . The Venue will have an address, and a short description. How do we edit it?

Option 1: All in the same object

Maybe, for simplicity, you add all the fields in the Event. It’s just two extra fields, right? Well, that’s certainly an option. If you are not going to pay much attention to the venues, then maybe that’s the way to go. If you’re going to use the venues (for instance, reuse them between events), then they should be a separate object.

Option 2: #accepts_nested_attributes_for:

Rails has a great tool in it’s belt for saving multiple objects at a time: #accepts_nested_attributes_for: . This works relatively simply, you just embed your objects in the form using #fields_for and you use _attributes from the controller side.

<%= form_for :event do %> <%= input_tag :price %> <%= fields_for :venue do %> <%= input_tag :address %> <%= input_tag :description %> <% end %> <% end %>

That will sent to the browser this hash:

{event: {id: 1, price: '50', venue_attributes: { address: '308 Negra Arroyo lane', description: 'The house with the pizza in the roof' }}}

The only requirement you have in your model, is to add:

class Event < ActiveRecord::Base belongs_to :venue accepts_nested_attributes_for :venue end

It’s oh so simple. The venue belongs to the event. The concept of composition is clear in the form, and in the controller, and Rails uses that to simplify a lot of work.

This, however, leads down a dangerous path. The Event , from now on, is responsible for managing the Venue . It’s a lot of responsibility that we would like to keep separate. Why is that? For one, we don’t need the Event to have the burden of managing the Venue all the time. That forces the programmer working on the system to think about the Venue every time he’s working with the Event . And also, any changes in Venue might impact Event , which is a class that’s going to be very important for our code base, so we should try to keep it as clean as possible.

Option 3: Form Object to the rescue

What we can do, is create a new Form Object. This one will handle the updating of the Details in the Event .

You should note that we’re not changing the original form object, but creating a new one. We do that so we can keep the two logics separated.

class EventDetailsForm < Syrup::FormObject wraps :event accepts_nested_attributes_for :venue form_name :event_details def after_find(params) event.build_venue if event.venue.nil? end end

As you can see, the logic is much more clear, as only the relevant parts of it are in this class and no logic regarding the other controller and form is available.

We set a form_name so that the routes generated are more user friendly (like /events_details/4/edit ). And the #after_find method deals with the case of an event not having yet a venue, which then creates an empty one.

Conclusion

As you can see, Form Objects are a good way of organizing the logic in your application, maintaining part of the logic that does not belong neither in the model or the controller in a separate entity.