This is the third article in a series about best practices for creating Rails forms. Be sure to check out Pretty Data, Pretty Code and Modeling All Form Data for more related techniques.
Revisiting Our Form
Continuing with the example from my last article, I have this simple form:
<% form_for :product, :url => products_path do |f| %> <div class="form_item text_field"> <label for="product[name]">Name:</label> <%= f.text_field :name %> </div> <div class="form_item text_field"> <label for="product[price]">Price:</label> <%= f.text_field :price %> </div> <div class="form_item text_field"> <label for="product[features]">Features (1 per line):</label> <%= f.text_area :features %> </div> <div class="form_item submit_button"> <%= f.submit "Create Product" %> </div> <% end %>
Note: I changed the original form's wrapper tags from <p> to <div>. The reason is that, if you're using the built-in Rails error message helpers, by default it will wrap erroneous fields in <div> tags. Since <div> tags can't nest within <p> tags, this can cause serious layout-breaking problems.
There's a lot of duplication here in terms of the wrapper code. Each field is wrapped in a <div> with a class of form_item as well as a descriptor of what kind of field it contains. I use this information for CSS styling of specific kinds of inputs. The labels are also fairly repetitive as well, and these repetitions would become more obnoxious were this a longer form.
Removing Repetition
What if we could generate this same form code while removing the repetition? It's possible, and Rails gives us a great hook for just this scenario by allowing us to build custom Form Builders. A custom Form Builder is simply a subclass of ActionView::Helpers::FormBuilder that can alter and extend the abilities of the regular Form Builder. In our current form, the f block variable is an instance of FormBuilder, so methods like text_field and submit are all instance methods of the FormBuilder class. Let's override these methods to not only output the field markup, but to also output the wrapper div:
# in /helpers/application_helper.rb class WrapperFormBuilder < ActionView::Helpers::FormBuilder METHODS_TO_OVERRIDE = %w{text_field text_area password_field file_field date_select datetime_select submit} METHODS_TO_OVERRIDE.each do |method_name| src =<<END_SRC def #{method_name}_with_wrapper(field, options={}) # allow explicit setting of label text with options[:label] field_label = if '#{method_name}' == 'submit' '' # no label for submit inputs elsif options[:label] label(field, options.delete(:label)) else label(field) + ":" # Adds colon as default end # get unwrapped field field_markup = #{method_name}_without_wrapper(field, options) # return wrapped field (@template gives us access to helper methods in this class) @template.content_tag(:div, field_label + field_markup, :class => "form_item #{method_name}") end END_SRC class_eval src, __FILE__, __LINE__ alias_method_chain method_name.to_sym, :wrapper end end
For our form, we can now implement our new class like this:
<% form_for :product, :url => products_path, :builder => WrapperFormBuilder do |f| %>
Another possibility is to create a new method to replace form_for:
# in helpers/application_helper.rb def wrapper_form_for(name, object=nil, options={}, &proc) form_for(name, object, options.merge(:builder => WrapperFormBuilder), &proc) end
Using this new helper method, our form now looks like this:
<% wrapper_form_for :product, :url => products_path do |f| %> <%= f.text_field :name %> <%= f.text_field :price %> <%= f.text_area :features, :label => "Features (1 per line):" %> <%= f.submit "Create Product" %> <% end %>
If you need to create a form field without a wrapper (perhaps to use a non-conforming wrapper), you can still access the original methods like f.text_field_without_wrapper or f.submit_without_wrapper.
I have found custom Form Builders to be powerful tools for speeding up form development, DRYing up code and keeping consistency between forms and developers. This is a fairly basic example, but the sky is the limit for what you can implement using these techniques.

