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
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
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
FormResponseneeds to run the validations defined in
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.
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
1 2 3 4 5 6 7 8 9 10 11
FormResponse#create action uses the same
#single_response_for1 method that we used in
new. To understand what is happening we have to look at the
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.
FormComponent for that
Form is used in
attr_accessor and, depending on the options, any of the standard Rails validations.
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
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!