You might have heard of “fat models” referring to (mostly) ActiveRecord models turning into huge classes responsible for everything from user authentication to accepting attributes for entirely different models. This violates one of my favourite rules of [SOLID][solid], the single responsibility principle.
One of the patterns you can use to help balance your SOLID karma is with form objects. We use form objects extensively to back our forms and encapsulate the data submitted by a user.
It’s often tempting to reach for the ever ready
ActiveRecord model when
building forms in Rails because it Just Works™ with the Rails Form
Helpers. I’m going to show you how to have your cake and eat it by backing
your forms with objects.
Why use form objects?
When looking to refactor your app it’s always a good idea to keep the single responsibility principle in mind.
…the single responsibility principle states that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class…
SRP helps you make better design decisions around what a class should be responsible for. Your database table model for example (an ActiveRecord model in the context of Rails) represents a single database record in code, there is no reason for it to be concerned with anything your user is doing.
This is where form objects come in. A form object is responsible for representing a form in your application. So each input field can be treated as an attribute in the class, it can validate that those attributes meet some rules and it pass the “clean” data to where it needs to go. This could be your database models or perhaps your search query builder.
Using Virtus and ActiveModel to create form objects
Building a form object is really easy, Rails 4+ provides
ActiveModel::Model so it “quacks” like an ActiveRecord object
and works seamlessly with
form_for, including giving you access to all the
validation helpers you’re already familiar with including methods such as
Virtus provides a neat DSL to define attributes on the object with useful options such as defining the data type and default value - very similar to what ActiveRecord is doing behind the scenes. Virtus is not essential for your form object but it saves a lot of boilerplate code.
The foundation for your form object is simple, just include
Virtus. Although form objects are technically models I prefer to create a
new directory called
forms in the
app directory to store my form objects
separately from my ActiveRecord models.
I often find people are scared to create directories outside of the Rails scaffold but you don’t need to be, Rails will load them all - just remember to restart your server processes when you do.
A form object might look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
You’ll notice we’ve created two database records in two different tables from this single form. Your form objects don’t need to be any more complicated than that. The same is true in your controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
This should look very similar to how you might already be working with your
ActiveRecord models. And finally just for completeness in the view with
1 2 3 4 5 6 7 8 9 10 11 12
And there we have it, this form object is responsible for user input for this form and handing it off to the other models for saving to the database.
What does this mean for your ActiveRecord models?
Now your model doesn’t directly accept a user’s input then you can remove your validations from the model because your form object is taking care of that.
If that makes you nervous consider where any invalid data would be reaching as far as your database model without being valid and do something about it before it gets that far.
Need i18n support? It’s already there!
Because we’re backed by
(i18n for short) is already baked in so you can set the attribute labels and
validation messages the same as you can when working with ActiveRecord models.
1 2 3 4 5 6 7 8 9 10 11
With form objects we are not attempting to replace Rails “magic” in fact we are working with it to leverage more power and flexibility by modelling our forms on the data we want them to receive from the user and not whatever columns are in our database table. They also allow you to back forms that might not have a map to a database table such as a search form.
By decoupling from the database schema you allow yourself to make better design decisions around your business logic instead of trying to force your database schema to map to your views so that your form helper will work.