Since 2005 I have maintained a CMS developed on the Rails platform for my day job. There are a number of people who maintain the content on that page and one of the most requested features was for the content staff to create “dynamic” forms.
Over time our implementation failed for a variety of reasons, mostly due to improvements to Ruby and the Rails framework. The most recent issue hit us with Rails version 3.2.x and completely broke the code. Remedying the issue require rewriting the validation aspects of the code.
Before looking at the fix, I’ll describe how the dynamic forms work. From the administrative section a new Form
can be created. A Form
has many FormComponents
that define the various questions and accepted answers. A FormResponse
then takes the questions and answers provided and saves it as an “isolated” snapshot. This allows the form to be updated at-will but does not break the history of responses. For example, a particular option may no longer be available or a question from a form my be removed entirely.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
A FormComponent
can be any of the standard form elements, including selects, check boxes, and radio buttons. Accepted answers are stored in a comma separated list and the form is rendered using Rails standard form helpers. Additionally, a form component can be determined to be required, accepted, or match a specific regular expression.
The following code snippet shows creating a FormComponent
called full_name
that is a required field within the form.
1 2 3 |
|
There are three hurdles to jump for this to work.
- We don’t know what the questions are going to be for a given form in advance.
- The answers need to presented to the visitor in a form that will be saved in a
FormResponse
. FormResponse
needs to run the validations defined inFormComponent
The code to display the form fields is relatively straight-forward. Here are the relevant pieces.
1 2 3 4 5 |
|
1 2 3 4 5 6 7 |
|
To keep things DRY I’ve created a view partial for each kind of form component. Using the example FormComponent
from earlier, this form would use the _text_field.html.haml
partial which looks like this.
1 2 |
|
The other partials are not much more complicated and I am looking forward to the Rails 4 #collection_check_boxes method, which I think can clean them up even more. That is all that’s required to display the form for submission. Clicking submit will send the following parameters to FormResponse#create
.
1
|
|
1 2 3 4 5 6 7 8 9 10 11 |
|
The FormResponse#create
action uses the same #single_response_for
1 method that we used in new
. To understand what is happening we have to look at the FormResponse
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
It looks like a lot is happening in #single_response_for
but it isn’t doing anything overly complex. It starts by creating a Proc
that will be sent to dup
at the end. The Proc
is where all the unique methods and validations for this instance of FormResponse
will be defined.
Inside the Proc
each FormComponent
for that Form
is used in attr_accessor
and, depending on the options, any of the standard Rails validations.
Once the Proc
is defined it is passed to the dup
method, which we override to pass our custom block into the shallow copy of FormResponse
. The result is a unique instance of FormResponse
with attributes and validations defined by FormComponent
.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Solving this problem highlights many of the things I love about the Ruby language. If you’ve solved this issue another way, think this was terrible, or otherwise have a comment, please share!
- Thanks go to ahoward on Rails issue #5449 for a cleaner implementation than what I was originally doing. ↩