Carrot is Moby's nascent platform for running custom contests, promotions, and sweepstakes. In Carrot, program owners design custom entry forms, which participants fill out to submit their entries. Since Carrot aims to support a diverse array of contest types and is meant to be used by customers with widely different requirements, we needed a way to give owners a great deal of flexibility in their form designs.
Let’s assume the following requirements for the system:
- Forms and their submissions must be attachable to any record (e.g., programs and rounds)
- Owners can compose their forms from a variety of field types (defined by the application)
- Owners can add input validation rules
- Submitters can fill out the forms and see errors when their input is invalid
- Owners can view submissions of their forms
With our requirements in mind, we can work toward a solution. Requirement 1 tells us immediately that we’ll need two separate concepts to represent a single dynamic form system: the form itself (the fields that make it up), and a submission of the form (a group of filled-in fields). In our domain, forms are attached to
Rounds, while their submissions are attached to
Entrys. These two components, which I’ll call
Submission, will be present for each form system.
Requirement 2 suggests we’ll need the concept of a
FormField, and each
FormField will need a type. Furthermore, each
FormField will need to accommodate validation logic (requirement 3), but each field will not have the same types of validations. For example, an integer field might naturally have a
max and a
min option that owners can use to control the range of accepted inputs; but
min would not make sense in the context of a checkbox field. So it's clear that we’ll need to define the "meta-validation" of each field (essentially, the validation menu from which the creator can choose) separately.
Note: We'll be implementing this solution in Ruby on Rails, but you could easily adapt this approach to other platforms. In order to understand the solution we implement, you should be comfortable with Rails development—we won’' be showing how to create database tables or wire views into controllers. You'll also need to be familiar with polymorphic associations and single table inheritance.
A note about single-table inheritance: One of the biggest lessons we learned while implementing custom forms is that single-table inheritance is about trade-offs. While the subclassing provides an easy way to override behavior and validations, the cost comes when trying to change a field’s type. When this occurs, Rails attempts to apply the validation logic from the original model class, instead of the new one. To get around this limitation, you can first update the field’s type column, reload the field, and then process the validation logic.
The models are the first and most important step in this process. Remember that we want our forms and their submissions to be attachable to any pair of models, so we have to design our form models to be easily re-usable; that means outfitting them with polymorphic associations. Furthermore, we’ll extract the form and submission logic each into their own concern, so we can just include them in any pair of models.